# llms-full (private-aware)
> Built from GitHub files and website pages. Large files may be truncated.

--- .taskmaster/docs/prompt-bench_PRD.md ---
## 1) Overview

### Problem

Teams iterating on system prompts need a repeatable way to run a matrix of **system templates × user payloads × optional “skills” × providers** and capture outputs + metadata per run, without manual copy/paste across tools.

### Target users

* **Prompt engineers / evaluators** running systematic prompt experiments across multiple local/CLI model entrypoints.
* **Tooling maintainers** who want a stable runner core with an upgrade path to an MCP server wrapper.

### Why current solutions fail

* Ad-hoc scripts typically conflate prompt composition, provider invocation, and artifact capture, making runs non-reproducible and hard to compare across providers.
* Provider interfaces differ (OpenAI-compatible HTTP vs CLI tools), making consistent I/O capture and retries error-prone.

### Success metrics (proposed; not provided in spec)

* **Correctness:** 100% of runs obey composition rules (marker replacement / append rule, no trimming, preserves trailing blank lines).
* **Reproducibility:** every run directory contains required artifacts; batch emits `summary.json` and `config_snapshot.toml`.
* **Coverage:** executes full `(template × payload × skill × provider)` Cartesian product or a filtered/limited subset deterministically.
* **Robustness:** provider failures are captured with structured error metadata (non-zero exit, HTTP errors, parsing errors) rather than silent drops.

### Constraints / assumptions

* Python runtime; exact version unspecified (assume ≥3.11 for TOML parsing convenience, but design should allow fallback).
* Tools available locally: LM Studio server (OpenAI-compatible), Codex CLI, Gemini CLI; PATH/config may vary.
* “Skills” are injected into the **payload** as extra context (exact injection format not fully specified; treated as configurable).

---

## 2) Capability Tree (Functional Decomposition)

### Capability: Input Discovery & Loading

Discovers templates/payloads/skills via globs and loads file contents verbatim.

#### Feature: Discover template files (MVP)

* **Description:** Expand `templates/*.md` (configurable) into a stable ordered list.
* **Inputs:** Template glob(s), optional include/exclude filters.
* **Outputs:** List of template file descriptors `{path, name, bytes/text}`.
* **Behavior:** Glob → sort deterministically → keep full path identity.

#### Feature: Discover payload files (MVP)

* **Description:** Expand `payloads/*.md` into a stable ordered list.
* **Inputs:** Payload glob(s), optional include/exclude filters.
* **Outputs:** List of payload descriptors.
* **Behavior:** Same as templates.

#### Feature: Discover skill files (MVP)

* **Description:** Expand `skills/*.md` (or “none”) into a stable ordered list.
* **Inputs:** Skill glob(s), optional sentinel meaning “no skill”.
* **Outputs:** List of skill descriptors (including `{id: "none"}`).
* **Behavior:** Supports empty skills dimension.

#### Feature: Verbatim file read (MVP)

* **Description:** Read files without trimming/escaping; preserve all characters and line breaks.
* **Inputs:** File path.
* **Outputs:** Raw content string (and optionally raw bytes).
* **Behavior:** No normalization; preserves trailing blank lines.

---

### Capability: Prompt Composition Engine

Builds the composed system prompt from template + payload (+ skill injection).

#### Feature: Marker replacement composition (MVP)

* **Description:** Replace the **first** `{{USER_PROMPT}}` marker in the template with payload verbatim.
* **Inputs:** `template_text`, `payload_text`.
* **Outputs:** `composed_system_text`.
* **Behavior:** Replace first occurrence only; do not alter other text.

#### Feature: Append composition when marker absent (MVP)

* **Description:** If no marker exists, append exactly one newline, then `User prompt:`, then payload verbatim.
* **Inputs:** `template_text`, `payload_text`.
* **Outputs:** `composed_system_text`.
* **Behavior:** Ensures exactly one newline before `User prompt:` block; preserves trailing blank lines from template.

#### Feature: Skill injection into payload (MVP)

* **Description:** Inject optional skill context into the payload as an independently iterable dimension.
* **Inputs:** `payload_text`, `skill_text`, injection mode config.
* **Outputs:** `effective_payload_text`.
* **Behavior:** Default: prefix skill + delimiter + payload; configurable to suffix or “no delimiter”.

---

### Capability: Job Matrix Generation

Creates the Cartesian product and supports filtering/limits.

#### Feature: Cartesian job expansion (MVP)

* **Description:** Generate `(template × payload × skill × provider)` jobs.
* **Inputs:** Lists of templates/payloads/skills/providers.
* **Outputs:** Job list with stable job IDs.
* **Behavior:** Deterministic iteration order; each job references source file identities.

#### Feature: Provider filtering (MVP)

* **Description:** Run only selected providers.
* **Inputs:** Provider allowlist/denylist.
* **Outputs:** Filtered job list.
* **Behavior:** Filters before execution and reflected in summary metadata.

#### Feature: Job limiting (MVP)

* **Description:** Limit jobs to first N (or sampled subset).
* **Inputs:** `limit`, optional `seed`.
* **Outputs:** Limited job list.
* **Behavior:** Stable selection under deterministic ordering.

---

### Capability: Provider Execution Adapters

Runs a composed system prompt against each provider and returns the assistant text plus raw artifacts.

#### Feature: LM Studio adapter (MVP)

* **Description:** Call OpenAI-compatible `/v1/chat/completions` with system-only prompt; retry with minimal user message if required.
* **Inputs:** Endpoint URL, model, headers/api key (if any), `composed_system_text`.
* **Outputs:** Assistant response text; raw JSON; HTTP metadata; errors.
* **Behavior:** Attempt system-only; on rejection, fallback to minimal user message while preserving system content.

#### Feature: Codex CLI adapter (MVP)

* **Description:** Run `codex exec` non-interactively, piping prompt via stdin (PROMPT="-" semantics).
* **Inputs:** CLI path, args, env, `composed_system_text`.
* **Outputs:** Assistant response text (stdout-derived), stdout/stderr, exit code.
* **Behavior:** Captures stdout/stderr separately; non-zero exit becomes structured error.

#### Feature: Gemini CLI adapter (MVP)

* **Description:** Run Gemini CLI headless using stdin or `--prompt`; optionally `--output-format json`.
* **Inputs:** CLI path, args, output format mode, `composed_system_text`.
* **Outputs:** Assistant response text; stdout/stderr; parsed JSON if available.
* **Behavior:** Prefer JSON output when enabled; fallback to text parsing.

#### Feature: Provider timeout + retry policy (MVP)

* **Description:** Apply per-provider timeout and bounded retries with captured attempt metadata.
* **Inputs:** Timeout seconds, retry count, backoff policy.
* **Outputs:** Final result or structured error with attempts.
* **Behavior:** Retries only on configured retryable classes (HTTP 5xx, transient CLI failure).

---

### Capability: Artifact Capture & Storage

Writes per-run artifacts and batch summaries.

#### Feature: Per-job run directory (MVP)

* **Description:** Write one directory per job containing required artifacts.
* **Inputs:** Job ID, output root, composed system, provider outputs/meta.
* **Outputs:** Files: `composed_system.txt`, `output.txt`, `run.json`, plus provider-specific raw/stdout/stderr/meta files.
* **Behavior:** Atomic-ish write (temp then rename) to avoid partial artifacts on crash.

#### Feature: Batch-level artifacts (MVP)

* **Description:** Write `config_snapshot.toml` and `summary.json`.
* **Inputs:** Parsed config, run results.
* **Outputs:** Snapshot + summary.
* **Behavior:** `summary.json` includes totals, ok/error counts, and per-run pointers.

---

### Capability: Runner CLI

Primary UX entrypoint.

#### Feature: Run command (MVP)

* **Description:** `python promptbench.py run --config <config.toml>`.
* **Inputs:** Config path, overrides.
* **Outputs:** Exit code; logs; run directory.
* **Behavior:** Loads config → generates jobs → executes with concurrency → writes artifacts.

#### Feature: CLI overrides (MVP)

* **Description:** Override output dir, limit jobs, filter providers, set concurrency.
* **Inputs:** CLI flags.
* **Outputs:** Effective config applied to run.
* **Behavior:** Overrides recorded into `config_snapshot.toml`.

---

### Capability: Future MCP Server Wrapper (Non-MVP)

Exposes the runner via an MCP server interface without rewriting core.

#### Feature: MCP tools: list_files, run_batch, get_run (Non-MVP)

* **Description:** Provide programmatic access for listing inputs, triggering runs, retrieving run artifacts.
* **Inputs:** MCP tool calls; config payloads.
* **Outputs:** Structured responses and artifact references.
* **Behavior:** Thin wrapper over the same runner engine.

---

## 3) Repository Structure + Module Definitions (Structural Decomposition)

### Repository structure (proposed)

```
promptbench/
├── promptbench.py                  # CLI entrypoint (argparse/typer)
├── promptbench.example.toml        # Example config snapshot format
├── promptbench/
│   ├── __init__.py
│   ├── core/
│   │   ├── types.py                # dataclasses for config/jobs/results
│   │   ├── errors.py               # structured error types + serialization
│   │   ├── textio.py               # verbatim read/write helpers
│   │   ├── compose.py              # composition + skill injection
│   │   ├── config.py               # TOML parsing + validation
│   │   └── discovery.py            # glob discovery + deterministic ordering
│   ├── providers/
│   │   ├── base.py                 # ProviderAdapter interface
│   │   ├── lmstudio.py             # HTTP adapter
│   │   ├── codex_cli.py            # codex exec adapter
│   │   └── gemini_cli.py           # gemini cli adapter
│   ├── runner/
│   │   ├── matrix.py               # cartesian job generation + filtering/limit
│   │   ├── executor.py             # concurrency, retries, timeouts
│   │   └── summary.py              # batch summary construction
│   └── artifacts/
│       ├── layout.py               # run directory naming + paths
│       └── writer.py               # write composed_system/output/meta/raw files
└── mcp_server_example.py           # Non-MVP wrapper stub
```

(Names align to the spec’s suggested components: runner script, TOML config, templates/payloads/skills folders, MCP stub.)

### Module definitions (single responsibility + exports)

#### Module: `core/types.py`

* **Responsibility:** Canonical in-memory shapes for config, jobs, provider outputs, run results.
* **Exports:** `Config`, `ProviderSpec`, `JobSpec`, `RunResult`, `ProviderResult`, `ArtifactPaths`

#### Module: `core/errors.py`

* **Responsibility:** Structured error taxonomy and JSON-safe serialization.
* **Exports:** `PromptbenchError`, `ProviderError`, `ConfigError`, `IOErrorWrap`, `to_error_dict()`

#### Module: `core/textio.py`

* **Responsibility:** Verbatim file read/write and newline preservation policy.
* **Exports:** `read_text_verbatim(path)`, `write_text_verbatim(path, text)`

#### Module: `core/compose.py`

* **Responsibility:** Prompt composition rules + skill injection.
* **Exports:** `compose_system(template_text, payload_text)`, `inject_skill(payload_text, skill_text, mode)`

#### Module: `core/config.py`

* **Responsibility:** Parse/validate TOML config and produce `Config`.
* **Exports:** `load_config(path)`, `apply_overrides(config, overrides)`, `serialize_config(config)`

#### Module: `core/discovery.py`

* **Responsibility:** Discover inputs via globs and return stable ordered lists.
* **Exports:** `discover_files(globs, filters)`

#### Module: `providers/base.py`

* **Responsibility:** Provider adapter interface and shared execution contract.
* **Exports:** `ProviderAdapter`, `ProviderResponse`, `ProviderRunContext`

#### Module: `providers/lmstudio.py`

* **Responsibility:** LM Studio OpenAI-compatible HTTP execution.
* **Exports:** `LMStudioAdapter`

#### Module: `providers/codex_cli.py`

* **Responsibility:** Codex CLI execution and capture.
* **Exports:** `CodexCLIAdapter`

#### Module: `providers/gemini_cli.py`

* **Responsibility:** Gemini CLI execution and capture.
* **Exports:** `GeminiCLIAdapter`

#### Module: `runner/matrix.py`

* **Responsibility:** Build job matrix and apply filters/limits deterministically.
* **Exports:** `build_jobs(templates, payloads, skills, providers, filters, limit)`

#### Module: `runner/executor.py`

* **Responsibility:** Execute jobs with concurrency, retries, timeouts; emit `RunResult`s.
* **Exports:** `run_jobs(jobs, adapters, artifacts_writer, concurrency, policy)`

#### Module: `runner/summary.py`

* **Responsibility:** Aggregate results into batch summary structure.
* **Exports:** `build_summary(run_results)`

#### Module: `artifacts/layout.py`

* **Responsibility:** Path conventions and run directory layout builder.
* **Exports:** `make_run_dir(root, job_id, ...)`, `artifact_paths(run_dir)`

#### Module: `artifacts/writer.py`

* **Responsibility:** Write per-run required artifacts and provider-specific raw artifacts.
* **Exports:** `write_run_artifacts(paths, composed_system, provider_response, run_meta)`

#### Module: `promptbench.py`

* **Responsibility:** CLI wiring: parse args → call runner → exit code.
* **Exports:** `main()`

#### Module: `mcp_server_example.py` (Non-MVP)

* **Responsibility:** MCP exposure of runner operations (thin wrapper).
* **Exports:** `list_files()`, `run_batch()`, `get_run()`

---

## 4) Dependency Chain (layers, explicit “Depends on: […]”)

### Foundation Layer (Phase 0)

* **core/types**: No dependencies
* **core/errors**: No dependencies
* **core/textio**: Depends on: [core/errors]

### Input + Composition Layer (Phase 1)

* **core/config**: Depends on: [core/types, core/errors, core/textio]
* **core/discovery**: Depends on: [core/errors]
* **core/compose**: Depends on: [core/errors]

### Provider Layer (Phase 2)

* **providers/base**: Depends on: [core/types, core/errors]
* **providers/lmstudio**: Depends on: [providers/base, core/types, core/errors]
* **providers/codex_cli**: Depends on: [providers/base, core/types, core/errors]
* **providers/gemini_cli**: Depends on: [providers/base, core/types, core/errors]

### Runner Layer (Phase 3)

* **runner/matrix**: Depends on: [core/types, core/errors, core/discovery, core/config]
* **artifacts/layout**: Depends on: [core/types, core/errors]
* **artifacts/writer**: Depends on: [core/textio, core/errors, artifacts/layout]
* **runner/executor**: Depends on: [runner/matrix, providers/base, artifacts/writer, core/compose, core/errors]
* **runner/summary**: Depends on: [core/types, core/errors]

### CLI Layer (Phase 4)

* **promptbench.py**: Depends on: [core/config, runner/executor, runner/summary, artifacts/writer]

### MCP Layer (Phase 5, Non-MVP)

* **mcp_server_example.py**: Depends on: [runner/executor, runner/summary, core/config, artifacts/layout]

---

## 5) Development Phases (Phase 0…N; entry/exit criteria; tasks with dependencies + acceptance criteria + test strategy)

### Phase 0: Foundations

**Entry criteria:** Empty repo scaffold exists.
**Exit criteria:** Foundation modules importable; error serialization stable.

* [ ] Implement `core/types` (depends on: none)

  * Acceptance criteria: dataclasses cover config/jobs/results; JSON-serializable via `asdict`.
  * Test strategy: unit tests for (de)serialization and defaulting behavior.

* [ ] Implement `core/errors` (depends on: none)

  * Acceptance criteria: all error classes map to stable `code/message/details`.
  * Test strategy: unit tests for conversion to dict and nesting.

* [ ] Implement `core/textio` (depends on: [core/errors])

  * Acceptance criteria: round-trip read/write preserves trailing newlines and blank lines.
  * Test strategy: unit tests using golden files with varied newline endings.

---

### Phase 1: Inputs + Composition

**Entry criteria:** Phase 0 complete.
**Exit criteria:** Given template+payload(+skill), the composed system prompt matches spec exactly.

* [ ] Implement `core/discovery` (depends on: [core/errors])

  * Acceptance criteria: glob discovery returns deterministic order across runs.
  * Test strategy: unit tests with temp directories.

* [ ] Implement `core/config` (depends on: [core/types, core/errors, core/textio])

  * Acceptance criteria: loads TOML, validates required fields, can emit `config_snapshot.toml`.
  * Test strategy: unit tests with valid/invalid TOML fixtures.

* [ ] Implement `core/compose` (depends on: [core/errors])

  * Acceptance criteria:

    * Replaces only first `{{USER_PROMPT}}` occurrence when present.
    * Appends `User prompt:` block with exactly one newline when marker absent; preserves trailing blank lines.
  * Test strategy: golden tests covering marker/no-marker, multiple markers, empty payload, trailing blank lines.

---

### Phase 2: Provider Adapters

**Entry criteria:** Phase 1 complete.
**Exit criteria:** Each provider returns `(assistant_text, raw artifacts, meta, error)` in a unified shape.

* [ ] Implement `providers/base` (depends on: [core/types, core/errors])

  * Acceptance criteria: common `run(context)->ProviderResponse` contract used by all adapters.
  * Test strategy: unit tests with a stub adapter.

* [ ] Implement `providers/lmstudio` (depends on: [providers/base, core/types, core/errors])

  * Acceptance criteria: system-only attempt; fallback to minimal user message if rejected.
  * Test strategy: integration tests using a local fake HTTP server; unit tests for response parsing.

* [ ] Implement `providers/codex_cli` (depends on: [providers/base, core/types, core/errors])

  * Acceptance criteria: executes `codex exec`, captures stdout/stderr/exit code.
  * Test strategy: unit tests using a fake executable script in PATH.

* [ ] Implement `providers/gemini_cli` (depends on: [providers/base, core/types, core/errors])

  * Acceptance criteria: supports stdin/`--prompt`; optional JSON output parse.
  * Test strategy: unit tests with fake executable emitting JSON/text.

---

### Phase 3: Runner + Artifacts + Summary (MVP “usable” point)

**Entry criteria:** Phase 2 complete.
**Exit criteria:** End-to-end run produces run directories and `summary.json`.

* [ ] Implement `artifacts/layout` (depends on: [core/types, core/errors])

  * Acceptance criteria: stable run directory naming; no collisions for distinct jobs.
  * Test strategy: unit tests for naming determinism and uniqueness.

* [ ] Implement `artifacts/writer` (depends on: [core/textio, core/errors, artifacts/layout])

  * Acceptance criteria: writes required files per job: `composed_system.txt`, `output.txt`, `run.json`, and provider raw/stdout/stderr/meta.
  * Test strategy: integration tests validating file presence + contents.

* [ ] Implement `runner/matrix` (depends on: [core/types, core/errors, core/discovery, core/config])

  * Acceptance criteria: generates `(template×payload×skill×provider)` combos; supports provider filter + limit.
  * Test strategy: unit tests with small fixture sets; property-style test for counts.

* [ ] Implement `runner/executor` (depends on: [runner/matrix, providers/base, artifacts/writer, core/compose, core/errors])

  * Acceptance criteria: concurrency works; errors captured in `run.json`; continues other jobs.
  * Test strategy: integration tests with stub adapters that succeed/fail deterministically.

* [ ] Implement `runner/summary` (depends on: [core/types, core/errors])

  * Acceptance criteria: `summary.json` includes totals, ok/error counts, and per-run references.
  * Test strategy: unit tests for aggregation over mixed results.

---

### Phase 4: CLI UX

**Entry criteria:** Phase 3 complete.
**Exit criteria:** `python promptbench.py run --config …` works with overrides.

* [ ] Implement `promptbench.py` CLI (depends on: [core/config, runner/executor, runner/summary, artifacts/writer])

  * Acceptance criteria: supports `--config`, override output dir, limit, provider filter, concurrency.
  * Test strategy: e2e tests invoking CLI with stub providers; assert outputs on disk.

---

### Phase 5: MCP wrapper (Non-MVP)

**Entry criteria:** Phase 4 complete and runner stable.
**Exit criteria:** MCP server exposes `list_files`, `run_batch`, `get_run` using runner core.

* [ ] Implement `mcp_server_example.py` wrapper (depends on: [runner/executor, runner/summary, core/config, artifacts/layout])

  * Acceptance criteria: can start server; tool calls produce structured outputs; no runner code duplication.
  * Test strategy: integration tests calling tools directly in-process.

---

## 6) User Experience

### Personas

* **Experiment runner:** wants to run a full matrix quickly and inspect outputs and raw logs.
* **Tool integrator:** wants a stable config-driven runner core and later an MCP interface.

### Key flows (MVP)

1. User creates:

   * `templates/*.md`, `payloads/*.md`, `skills/*.md`
   * `config.toml` referencing globs and providers
2. Runs: `python promptbench.py run --config config.toml`
3. Inspects:

   * `runs/<job>/composed_system.txt`
   * `runs/<job>/output.txt`
   * `runs/<job>/run.json` (+ provider raw/stdout/stderr)
   * batch `summary.json` and `config_snapshot.toml`

### UX notes

* Deterministic ordering and stable IDs are critical for comparisons.
* Failures should be visible in `summary.json` and per-run `run.json`, not only in console output.
* Concurrency should default conservatively; configurable via CLI.

---

## 7) Technical Architecture

### System components

* **Discovery:** glob inputs → stable lists.
* **Composition engine:** template + (skill-injected) payload → composed system prompt with strict marker/append rules.
* **Matrix generator:** Cartesian product + filters/limits.
* **Provider adapters:** LM Studio HTTP, Codex CLI, Gemini CLI.
* **Executor:** concurrency + retries + timeouts; produces `RunResult`s.
* **Artifacts:** per-run directory writing + batch summary.

### Data models (key fields)

* `Config`: input globs; provider specs; concurrency; retries; output root.
* `JobSpec`: template ref, payload ref, skill ref, provider id, job id, timestamps.
* `ProviderResponse`: `text`, `raw_json?`, `stdout?`, `stderr?`, `meta`, `error?`.
* `RunResult`: status, duration, artifact paths, error dict.

### Provider I/O decisions

* **LM Studio:** call `/v1/chat/completions`; attempt system-only; fallback to minimal user message on rejection.
* **Codex CLI:** `codex exec` via subprocess; prompt via stdin; capture stdout/stderr separately.
* **Gemini CLI:** subprocess; prefer JSON output when configured; otherwise capture text.

### Artifact layout decisions

* Per spec, required artifacts include:

  * `composed_system.txt`, `output.txt`, provider raw/stdout/stderr/meta, and `run.json`; plus batch `config_snapshot.toml`, `summary.json`.
* Run directory naming should encode job ID and optionally human-readable fragments (template/payload/provider), while ensuring filesystem safety.

### Extensibility decisions

* Provider adapters implement a shared interface so adding a new provider is a new module, not a refactor.
* MCP wrapper is a thin façade over the runner modules.

---

## 8) Test Strategy

### Test pyramid targets

* **Unit:** ~70% (composition, config parsing, matrix generation, artifact pathing)
* **Integration:** ~25% (fake HTTP server for LM Studio adapter; fake CLIs for codex/gemini; artifact writer on disk)
* **E2E:** ~5% (CLI run with stub providers; validates full run directory + summary)

### Coverage minimums (proposed)

* Line: 85%+, Branch: 75%+, Function: 85%+

### Critical scenarios (by module)

* `core/compose`

  * Marker present: replace first occurrence only.
  * Marker absent: append with exactly one newline + `User prompt:` block; preserve trailing blank lines.
  * Skill injection: prefix/suffix modes produce expected effective payload.

* Provider adapters

  * LM Studio: system-only success; system-only reject → fallback path.
  * CLI adapters: stdout/stderr capture; non-zero exit becomes structured error.

* `artifacts/writer`

  * Required files always present on success; partial failures still produce `run.json` with error.

* `runner/executor`

  * Concurrency >1 does not corrupt artifacts; failures don’t stop other jobs.

---

## 9) Risks and Mitigations

### Technical risks

* **Prompt “verbatim” preservation across OS/newline modes**

  * Impact: High; breaks composition correctness.
  * Likelihood: Medium.
  * Mitigation: `textio` reads/writes with explicit newline handling + golden tests.

* **Provider variability (system-only rejection, streaming to stderr, output format drift)**

  * Impact: High.
  * Likelihood: High.
  * Mitigation: adapter-specific parsing, retries, robust stdout/stderr capture, structured errors.

* **Run directory collisions / unstable IDs**

  * Impact: Medium.
  * Likelihood: Medium.
  * Mitigation: job IDs derived from stable tuple (template path hash, payload hash, skill hash, provider id).

### Dependency risks

* **Local tool availability and PATH configuration for `codex` and `gemini`**

  * Mitigation: preflight checks per provider; clear error messages; provider-level enable/disable.

### Scope risks

* **Unspecified provider config details (models, flags)**

  * Mitigation: config schema allows arbitrary provider args/env; document per-provider examples.

* **Unspecified evaluation/scoring across outputs**

  * Mitigation: keep out of MVP; ensure artifacts make downstream scoring easy.

---

## 10) Appendix

### Source spec (primary)

* “Build ‘promptbench’ runner…” including composition rules, iteration matrix, provider support, artifacts, CLI UX, and MCP upgrade path.

### Glossary

* **Template:** System prompt template file (may contain `{{USER_PROMPT}}`).
* **Payload:** User request content inserted into the system prompt.
* **Skill:** Optional extra context injected into payload as a separate iteration dimension.
* **Composed system:** Final system message sent to providers after applying marker/append rules.

### Open questions (from spec + resolved-by-assumption)

* Provider configuration specifics (models, CLI flags, paths) are not provided.
* Output normalization/scoring is not provided (explicitly deferred).
* Skill injection exact formatting: assumed configurable (prefix default).


--- .taskmaster/docs/promptbench-tui.prd.md ---
## 1) Overview

### Problem

`promptbench` is currently a non-interactive CLI that prints basic status to stdout and writes artifacts to disk. It provides limited visibility into (a) what will run before execution (matrix preview), (b) live progress per job/provider, and (c) how to browse results/errors without leaving the terminal.

### Target users

* **Prompt engineers / evaluators** running batch benchmarks across templates/payloads/skills/providers.
* **Model ops / infra engineers** validating provider adapters and diagnosing failures.
* **CI users** wanting a deterministic, machine-readable mode (retain existing behavior).

### Why current solutions fail

* Output is “print statements + JSON files,” requiring manual navigation of `runs/<job_id>/...` artifacts.
* No interactive selection/filtering of templates/payloads/skills/providers prior to execution.
* No real-time TUI progress, cancellation, or structured error surfacing across concurrent jobs.

### Goals / success metrics (measurable)

* **Preview-to-run completion:** ≥80% of runs initiated via interactive flow without editing command flags.
* **Debug time reduction:** ≥50% reduction in median time to identify top failing provider/template combination (measured by time-to-first-actionable-error).
* **Run visibility:** 100% of job state transitions available via structured events (for TUI and non-TUI consumers).
* **Compatibility:** existing `promptbench` batch behavior remains available and produces the same artifact layout by default.

### Constraints, integrations, assumptions

* **Constraint:** requested TUI stack is Charmbracelet (Go): Bubble Tea + Lip Gloss + Glow/Glamour + Gum, with optional Crush integration patterns. ([GitHub][1])
* **Assumption:** keep the Python execution core (providers, composer, artifact writer) and add a Go TUI front-end that orchestrates and visualizes runs via a stable event protocol.
* **Integration:** must continue reading existing TOML config and running existing providers (LM Studio, codex CLI, gemini CLI).

---

## 2) Capability Tree (Functional Decomposition)

### Capability: Interactive Run Setup

Enables users to configure and validate a benchmark run from within the terminal UI.

#### Feature: Config file selection and validation (MVP)

* **Description:** Load a TOML config and display validation errors inline.
* **Inputs:** `config_path`, file contents.
* **Outputs:** Validated config model or structured validation errors.
* **Behavior:** Parse config, validate required sections/fields, present actionable errors (missing globs/providers, invalid composition mode).

#### Feature: Input discovery preview (MVP)

* **Description:** Show discovered templates/payloads/skills with counts and sample paths.
* **Inputs:** `templates_glob`, `payloads_glob`, optional `skills_glob`.
* **Outputs:** Discovered file lists + counts.
* **Behavior:** Execute the same glob discovery logic used today; show empty-state guidance when lists are empty.

#### Feature: Matrix preview + filtering (MVP)

* **Description:** Preview Cartesian product size and allow filtering by provider/template/payload/skill before running.
* **Inputs:** discovered file lists, providers list, user filters, optional limit.
* **Outputs:** Job plan (job specs) and expected total job count.
* **Behavior:** Build job matrix deterministically; update preview live as filters/limits change.

#### Feature: Preflight checks (MVP)

* **Description:** Validate provider executables/URLs and write-permissions to output root before starting.
* **Inputs:** provider specs, output_root path.
* **Outputs:** Pass/fail per provider + actionable remediation.
* **Behavior:** For CLI providers, check executable resolution; for LM Studio, check reachability; for output root, ensure directory creatable.

---

### Capability: Live Execution Monitoring

Expose run lifecycle across concurrent jobs and providers.

#### Feature: Structured run event stream (MVP)

* **Description:** Emit machine-readable lifecycle events for config load, discovery, job scheduling, job start, provider start/end, artifact write, job completion.
* **Inputs:** internal runner state transitions.
* **Outputs:** JSON Lines event stream (stdout) + optional file sink.
* **Behavior:** Every job transitions through states; emit events in-order per job with timestamps and correlation IDs.

#### Feature: Progress dashboard (MVP)

* **Description:** TUI view of overall progress, per-provider success/error counts, and currently running jobs.
* **Inputs:** event stream.
* **Outputs:** Rendered dashboard state.
* **Behavior:** Aggregate events into counters and lists; update on every event tick.

#### Feature: Job detail pane (MVP)

* **Description:** Inspect a selected job’s composed system, provider stdout/stderr, and error payload.
* **Inputs:** job_id selection + artifact paths/event payloads.
* **Outputs:** Scrollable text views; structured error view.
* **Behavior:** Load from artifacts when available; fall back to buffered event payload.

#### Feature: Cancel / graceful stop (MVP)

* **Description:** Allow user to cancel remaining work and gracefully stop running jobs where possible.
* **Inputs:** user cancel action.
* **Outputs:** cancellation event + final summary.
* **Behavior:** Stop scheduling new jobs; attempt to terminate subprocess-based providers; mark incomplete jobs as cancelled in summary.

---

### Capability: Results Exploration & Reporting

Make completed run outputs navigable and comparable in-terminal.

#### Feature: Results list with filters/sort (MVP)

* **Description:** Browse completed jobs, filter by status/provider/template/payload/skill, sort by duration/status.
* **Inputs:** summary data + run results.
* **Outputs:** Filtered list and selection.
* **Behavior:** Local filtering; no re-run required.

#### Feature: Artifact viewer (MVP)

* **Description:** View `composed_system.txt`, `output.txt`, and `run.json` from within the TUI.
* **Inputs:** artifact paths per job.
* **Outputs:** Rendered text/JSON.
* **Behavior:** Read files verbatim and render with styling; large files handled with pagination/virtualized scrolling.

#### Feature: Markdown rendering for outputs/docs (MVP)

* **Description:** Render markdown outputs and help docs nicely in-terminal.
* **Inputs:** markdown content (e.g., provider output, docs).
* **Outputs:** styled terminal rendering.
* **Behavior:** Use Glow/Glamour-compatible rendering for markdown presentation. ([GitHub][2])

#### Feature: Export/share run summary (MVP)

* **Description:** Save a selected filtered view or aggregate stats to JSON.
* **Inputs:** current filters + results.
* **Outputs:** JSON summary file.
* **Behavior:** Deterministic export; include tool version, config hash, and filter criteria.

---

### Capability: Non-TUI Interactive Helpers

Provide lighter-weight interactivity for scripts and constrained terminals.

#### Feature: Gum-powered “wizard” mode (Non-MVP)

* **Description:** Offer a guided selection flow using Gum for shell workflows.
* **Inputs:** discovered inputs + providers.
* **Outputs:** resolved CLI command invocation.
* **Behavior:** Use Gum prompts (choose/input/confirm) and then execute normal run. ([GitHub][3])

---

### Capability: Extensibility & Ecosystem Alignment

Keep the system evolvable and consistent with Charmbracelet UX patterns.

#### Feature: Theme system and layout primitives (MVP)

* **Description:** Centralize styling, spacing, and common UI components.
* **Inputs:** theme config, terminal capabilities.
* **Outputs:** consistent styling across screens.
* **Behavior:** Use Lip Gloss style definitions; adapt colors with compatibility patterns where needed. ([GitHub][4])

#### Feature: Optional “open in Crush” handoff (Non-MVP)

* **Description:** From a run directory, allow launching Crush for deeper investigation/iteration.
* **Inputs:** run directory path, user action.
* **Outputs:** subprocess launch.
* **Behavior:** Spawn `crush` in the selected artifact root (best-effort; optional dependency). ([GitHub][5])

---

## 3) Repository Structure + Module Definitions (Structural Decomposition)

### Proposed repository structure

```
repo-root/
├── promptbench/                         # Existing Python core (kept)
│   ├── cli.py                           # Existing argparse CLI (kept)
│   ├── core/
│   │   ├── config.py
│   │   ├── discovery.py
│   │   ├── compose.py
│   │   ├── errors.py
│   │   ├── types.py
│   │   └── events.py                    # NEW: event types + emit helpers
│   ├── runner/
│   │   ├── executor.py
│   │   ├── matrix.py
│   │   ├── summary.py
│   │   └── eventing_executor.py         # NEW: wrapper that emits events
│   └── artifacts/
│       ├── layout.py
│       └── writer.py
├── tui/                                 # NEW: Go TUI application
│   ├── cmd/promptbench-tui/main.go
│   └── internal/
│       ├── app/                         # Bubble Tea models + update/view
│       ├── theme/                       # Lip Gloss styles, layout tokens
│       ├── protocol/                    # JSONL event schema + decoder
│       ├── runner/                      # python subprocess runner + control
│       ├── screens/                     # setup, run, results, help
│       └── widgets/                     # reusable UI components
└── docs/
    ├── tui.md                            # User guide rendered in-app
    └── troubleshooting.md
```

### Module definitions (Python)

#### Module: `promptbench.core.events`

* **Responsibility:** Define the run event schema and safe serialization helpers.
* **Exports:**

  * `EventType` (enum/string constants)
  * `RunEvent` (dataclass/dict shape)
  * `emit_event(writer, event)` (writes JSONL line)
  * `event_now()` (timestamp helper)

#### Module: `promptbench.runner.eventing_executor`

* **Responsibility:** Execute jobs while emitting lifecycle events; delegate artifact writing to existing writer.
* **Exports:**

  * `run_jobs_with_events(jobs, config, adapters, event_sink) -> results`

#### Module: `promptbench.cli_tui_bridge` (optional wrapper)

* **Responsibility:** Provide a Python CLI entry point that launches the Go TUI binary if installed.
* **Exports:**

  * `main()` (dispatch: `promptbench tui ...`)

### Module definitions (Go)

#### Module: `tui/internal/protocol`

* **Responsibility:** Decode/validate JSONL events from Python.
* **Exports:**

  * `type Event struct { ... }`
  * `DecodeStream(r io.Reader) (<-chan Event, <-chan error)`
  * `Validate(Event) error`

#### Module: `tui/internal/runner`

* **Responsibility:** Start/stop the Python runner process and manage IO streams (events, stderr).
* **Exports:**

  * `StartRun(cfgPath string, args RunArgs) (RunHandle, error)`
  * `StopRun(handle RunHandle) error`

#### Module: `tui/internal/theme`

* **Responsibility:** Centralize Lip Gloss styles and layout constants.
* **Exports:**

  * `Theme` struct
  * `DefaultTheme() Theme`
  * `Styles(Theme) ...`

#### Module: `tui/internal/widgets`

* **Responsibility:** Reusable Bubble Tea widgets (tables, spinners, logs, tabs).
* **Exports:** widget models implementing Bubble Tea interfaces.

#### Module: `tui/internal/screens`

* **Responsibility:** Screen-level state machines: setup, run dashboard, results explorer, help/docs.
* **Exports:** `NewSetupScreen(...)`, `NewRunScreen(...)`, `NewResultsScreen(...)`

#### Module: `tui/internal/app`

* **Responsibility:** App composition and navigation; root Bubble Tea model.
* **Exports:** `NewApp(...) tea.Model`, `Run(...) error`

---

## 4) Dependency Chain (layers, explicit “Depends on: […]”)

### Foundation Layer (Phase 0)

* **Python: `promptbench.core.events`**: event schema + serialization.
  **Depends on:** []
* **Go: `tui/internal/protocol`**: event decoding/validation.
  **Depends on:** []
* **Go: `tui/internal/theme`**: styling primitives with Lip Gloss.
  **Depends on:** [] ([GitHub][4])

### Execution Layer (Phase 1)

* **Python: `promptbench.runner.eventing_executor`**: emit events during execution.
  **Depends on:** [`promptbench.core.events`, existing `runner.executor`, `artifacts.writer`].
* **Go: `tui/internal/runner`**: manage Python subprocess + streams.
  **Depends on:** [`tui/internal/protocol`]

### UI Composition Layer (Phase 2)

* **Go: `tui/internal/widgets`**: tables/log panes/spinners.
  **Depends on:** [`tui/internal/theme`]
* **Go: `tui/internal/screens`**: setup/run/results/help screens.
  **Depends on:** [`tui/internal/widgets`, `tui/internal/runner`, `tui/internal/protocol`]
* **Go: `tui/internal/app`**: navigation + global state.
  **Depends on:** [`tui/internal/screens`]
* **Go: markdown rendering integration** (in `widgets` or `screens`).
  **Depends on:** [`tui/internal/theme`] and markdown renderer (Glow/Glamour usage). ([GitHub][2])

### Integration Layer (Phase 3)

* **Python: `promptbench cli tui bridge`** (optional): unified entry point.
  **Depends on:** [`tui` binary presence detection]

(Acyclic: all arrows flow Foundation → Execution → UI → Integration.)

---

## 5) Development Phases (Phase 0…N; entry/exit criteria; tasks with dependencies + acceptance criteria + test strategy)

### Phase 0: Event protocol + styling foundations

**Entry criteria:** existing codebase builds/runs via current CLI.

**Tasks (parallelizable):**

* [ ] Implement `promptbench.core.events` (depends on: none)

  * **Acceptance criteria:** emits valid JSONL lines with `type`, `ts`, `job_id?`, `payload`.
  * **Test strategy:** unit tests for serialization; golden-file JSONL examples.
* [ ] Implement `tui/internal/protocol` decoder (depends on: none)

  * **Acceptance criteria:** can decode a recorded JSONL event log into strongly typed events; rejects invalid events.
  * **Test strategy:** unit tests with recorded fixtures; fuzz test for malformed JSON lines.
* [ ] Implement `tui/internal/theme` using Lip Gloss (depends on: none)

  * **Acceptance criteria:** theme provides consistent styles for headers, tables, errors, status chips.
  * **Test strategy:** snapshot tests of rendered strings for key components (width-bounded). ([GitHub][4])

**Exit criteria:** protocol fixtures round-trip (Python emit → Go decode) with no loss of required fields.

**Delivers:** a stable cross-language contract the rest of the system can build on.

---

### Phase 1: Eventing execution + process control

**Entry criteria:** Phase 0 complete.

**Tasks (parallelizable):**

* [ ] Add `promptbench.runner.eventing_executor` (depends on: `promptbench.core.events`)

  * **Acceptance criteria:** emits events for: config loaded, discovery complete, jobs built, job started, provider started/ended, artifacts written, job completed, summary written.
  * **Test strategy:** integration test running a tiny job set with a stub provider adapter and asserting event sequence.
* [ ] Add `tui/internal/runner` subprocess management (depends on: `tui/internal/protocol`)

  * **Acceptance criteria:** starts Python run, streams events, surfaces stderr separately, supports user cancel signal.
  * **Test strategy:** integration test with a fake Python emitter process; verify stop behavior and stream closure.

**Exit criteria:** running the Python eventing executor produces a complete event stream consumable by Go.

**Delivers:** headless “engine mode” usable by both TUI and future automation.

---

### Phase 2: TUI MVP (setup → run → results)

**Entry criteria:** Phase 1 complete.

**Tasks (parallelizable):**

* [ ] Setup screen (depends on: `tui/internal/runner`, `tui/internal/widgets`)

  * **Acceptance criteria:** choose config path, show validation errors, show discovery counts, preview job count.
  * **Test strategy:** model tests for state transitions given synthetic events/errors.
* [ ] Run dashboard screen (depends on: `tui/internal/widgets`, `tui/internal/protocol`)

  * **Acceptance criteria:** live progress (done/total), per-provider stats, running queue, error count; cancel works.
  * **Test strategy:** feed recorded event logs; assert derived counters and rendered view invariants.
* [ ] Results explorer screen (depends on: setup+run screens, protocol)

  * **Acceptance criteria:** list jobs with filters; open artifact viewer for composed/output/run.json.
  * **Test strategy:** integration test with a real temp runs directory; verify file loads and navigation.

**Exit criteria:** smallest end-to-end usable path:

1. load config → 2) preview matrix → 3) run → 4) browse results.

**Delivers (MVP):** usable TUI runner built on Bubble Tea. ([GitHub][1])

---

### Phase 3: Markdown rendering + help/docs

**Entry criteria:** Phase 2 complete.

**Tasks (parallelizable):**

* [ ] Markdown viewer widget (depends on: `tui/internal/theme`)

  * **Acceptance criteria:** renders markdown outputs and docs in-terminal with stable formatting.
  * **Test strategy:** golden markdown fixtures rendered to terminal strings. ([GitHub][2])
* [ ] In-app help/docs screen (depends on: markdown viewer)

  * **Acceptance criteria:** shows `docs/tui.md` and troubleshooting guidance, searchable or navigable.

**Exit criteria:** users can read docs and markdown outputs inside the TUI.

**Delivers:** fewer context switches to external editors.

---

### Phase 4: Script-friendly mode + optional ecosystem hooks

**Entry criteria:** Phase 3 complete.

**Tasks (parallelizable):**

* [ ] Gum wizard command (depends on: Phase 1 eventing execution)

  * **Acceptance criteria:** guided selection emits a deterministic CLI invocation and runs it; works in non-fullscreen terminals.
  * **Test strategy:** shell-level tests in CI using recorded stdin inputs. ([GitHub][3])
* [ ] Optional “open in Crush” action (depends on: results explorer)

  * **Acceptance criteria:** if `crush` present, launches in selected run dir; otherwise shows non-fatal guidance.
  * **Test strategy:** integration test that stubs PATH to include a fake `crush`. ([GitHub][5])

**Exit criteria:** scripted interactivity and optional tooling hooks are available without impacting MVP.

---

## 6) User Experience

### Personas

* **Batch runner:** wants fast selection, preview, and a clear “what’s running / what failed.”
* **Debugger:** wants immediate access to stderr, provider error payloads, and artifacts.
* **CI operator:** wants structured outputs and unchanged non-interactive mode.

### Key flows

1. **Open TUI → Select config → Preview matrix → Filter → Run**
2. **During run → See progress → Drill into job → Cancel if needed**
3. **After run → Filter failures → Open artifacts → Export summary**

### UI/UX notes tied to capabilities

* Use Bubble Tea’s stateful model update loop for responsive progress and navigation. ([GitHub][1])
* Use Lip Gloss tokens for consistent spacing, panels, and status indicators across screens. ([GitHub][4])
* Render markdown (docs/output when applicable) in a dedicated viewer with paging. ([GitHub][2])
* Provide keyboard-first navigation: `/` filter, `enter` open, `esc` back, `q` quit, `c` cancel run.

---

## 7) Technical Architecture

### System components

* **Python engine (existing):** config loading, discovery, matrix building, provider execution, artifact writing.
* **Event protocol (new):** JSONL event stream emitted by Python.
* **Go TUI (new):** Bubble Tea app that runs/monitors Python process and provides result exploration.

### Data models

* **RunEvent (JSONL):**

  * `ts` (UTC RFC3339), `type`, `run_id`, optional `job_id`, `provider_id`, `payload` (typed per event)
* **Derived UI state:**

  * job map keyed by `job_id` with status, duration, artifact root, last stderr snippet, etc.

### APIs and integrations

* **Process interface:** Go starts Python in “eventing mode,” reads stdout as JSONL events, reads stderr separately.
* **Artifact interface:** Go reads artifacts from run directories produced by existing writer (`composed_system.txt`, `output.txt`, `run.json`).

### Key decisions (with rationale)

* **Keep Python execution core; add Go TUI:** avoids porting providers/executor/artifact logic while enabling Charmbracelet UX.
* **Event stream as the contract:** enables both TUI and future non-TUI consumers (CI dashboards, log collectors).
* **Use Bubble Tea + Lip Gloss:** standard Charmbracelet approach for stateful TUIs and consistent styling. ([GitHub][1])
* **Markdown rendering via Glow/Glamour ecosystem:** aligns with terminal markdown UX expectations in Charm tools. ([GitHub][2])
* **Versioning note:** Bubble Tea and Lip Gloss have evolving major versions/import paths; isolate library usage behind internal packages (`theme`, `widgets`) to reduce churn impact. ([GitHub][6])

---

## 8) Test Strategy

### Test pyramid targets

* **Unit tests (70%)**

  * Python event schema + serialization
  * Go protocol decode/validate
  * Bubble Tea model update logic
* **Integration tests (25%)**

  * Go runner starting a Python emitter and consuming events
  * Artifact reading from temp run dirs
* **E2E tests (5%)**

  * “happy path” run with a small fixture config and stub providers; verify TUI can complete and export summary

### Coverage minimums

* Unit-level modules: ≥80% line coverage for protocol/eventing code paths.
* Integration: cover cancel path and at least one provider error scenario.

### Critical scenarios per module

* **Python eventing executor:** out-of-order futures completion still yields correct per-job final states (since current executor collects results as futures complete).
* **Go protocol:** malformed JSON line does not crash app; surfaces as a non-fatal error with context.
* **TUI run dashboard:** handles high-frequency events without UI lockups; retains last N log lines.
* **Results explorer:** missing artifact files handled gracefully (partial runs/cancelled runs).

---

## 9) Risks and Mitigations

### Technical risks

* **Risk:** Cross-language contract drift (Python emits fields Go doesn’t expect).

  * **Impact:** High
  * **Likelihood:** Medium
  * **Mitigation:** version the event schema; add compatibility tests using recorded fixtures; enforce validation in Go.
  * **Fallback:** allow Go to treat unknown fields as opaque payload and continue.

* **Risk:** Cancellation semantics differ across providers (HTTP vs subprocess).

  * **Impact:** Medium
  * **Likelihood:** Medium
  * **Mitigation:** define “best-effort cancel” and “stop scheduling new jobs” as baseline; implement per-provider cancel hooks later.
  * **Fallback:** cancel stops new jobs and exits after current jobs complete.

* **Risk:** Library churn (Bubble Tea/Lip Gloss major version changes).

  * **Impact:** Medium
  * **Likelihood:** Medium
  * **Mitigation:** encapsulate in `theme/widgets`; pin versions; add compile-time CI checks. ([GitHub][6])

### Dependency risks

* **Risk:** Users lack Go binary installation path.

  * **Impact:** Medium
  * **Likelihood:** Medium
  * **Mitigation:** ship prebuilt binaries; optional Python bridge command that detects/launches binary; document install.

### Scope risks

* **Risk:** Over-building “IDE-like” features in TUI.

  * **Impact:** Medium
  * **Likelihood:** High
  * **Mitigation:** enforce MVP as setup→run→results only; defer advanced comparisons and integrations (Crush, gum wizard) to later phases.

---

## 10) Appendix

### References (Charmbracelet stack)

* Bubble Tea (TUI framework). ([GitHub][1])
* Lip Gloss (terminal styling). ([GitHub][4])
* Glow (markdown CLI) and Glamour (markdown rendering library). ([GitHub][2])
* Gum (interactive CLI utilities). ([GitHub][3])
* Crush (optional ecosystem hook). ([GitHub][5])

### Codebase context

* Current `promptbench` CLI, runner, providers, and artifact layout.

### Open questions (non-blocking; can be resolved during Phase 0–1)

* Event schema granularity: do we need token/stream events for provider stdout, or only final artifacts?
* Should TUI parse TOML directly in Go for richer setup UI, or delegate all config parsing to Python and only visualize results?
* What is the minimum acceptable cancel behavior per provider type (HTTP vs subprocess) for MVP?

[1]: https://github.com/charmbracelet/bubbletea?utm_source=chatgpt.com "charmbracelet/bubbletea: A powerful little TUI framework"
[2]: https://github.com/charmbracelet/glow?utm_source=chatgpt.com "charmbracelet/glow: Render markdown on the CLI, with ..."
[3]: https://github.com/charmbracelet/gum?utm_source=chatgpt.com "charmbracelet/gum: A tool for glamorous shell scripts"
[4]: https://github.com/charmbracelet/lipgloss?utm_source=chatgpt.com "charmbracelet/lipgloss: Style definitions for nice terminal ..."
[5]: https://github.com/charmbracelet/crush?utm_source=chatgpt.com "charmbracelet/crush: The glamourous AI coding agent for ..."
[6]: https://github.com/charmbracelet/bubbletea/releases?utm_source=chatgpt.com "Releases · charmbracelet/bubbletea"


--- docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/packs/bugs.md ---
# Oracle Pack — Unknown (Grouped Tickets Stage 1 — Direct Attach)

## Parsed args
- codebase_name: Unknown
- out_dir: docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs
- oracle_cmd: oracle
- oracle_flags: --files-report
- extra_files: 
- ticket_root: .tickets
- ticket_glob: **/*.md
- ticket_paths: .tickets/bugs/Parent Ticket Summary.md
- ticket_max_files: 1
- group_name: bugs
- group_slug: bugs
- mode: tickets-grouped-direct

Notes (contract):
- Exactly one fenced `bash` block in this document.
- No other ``` fences anywhere.
- Exactly 20 steps, numbered 01..20 in order.
- Step header: `# NN) ROI=... impact=... confidence=... effort=... horizon=... category=... reference=...`
- Every step includes: `--write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/NN-<slug>.md"` (double quotes required).
- Steps must be self-contained and must not rely on shell variables created in previous steps.
- Each step must attach tickets directly (no `_tickets_bundle.md` dependency).
- Pack ends with a Coverage check section listing all 10 categories as OK or Missing(<step ids>).

```bash
set -euo pipefail

mkdir -p "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs"

# 01) ROI=4.8 impact=6 confidence=0.80 effort=1 horizon=Immediate category=contracts/interfaces reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/01-contracts-interfaces-ticket-surface-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/01-contracts-interfaces-ticket-surface-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/01-contracts-interfaces-ticket-surface-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/01-contracts-interfaces-ticket-surface-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 02) ROI=4.6 impact=6 confidence=0.78 effort=1 horizon=Immediate category=contracts/interfaces reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/02-contracts-interfaces-integration-points-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/02-contracts-interfaces-integration-points-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/02-contracts-interfaces-integration-points-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/02-contracts-interfaces-integration-points-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: bugs)

Reference: bugs
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 03) ROI=5.1 impact=7 confidence=0.74 effort=1 horizon=NearTerm category=invariants reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/03-invariants-invariant-map-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/03-invariants-invariant-map-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/03-invariants-invariant-map-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/03-invariants-invariant-map-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 04) ROI=5.0 impact=7 confidence=0.72 effort=2 horizon=NearTerm category=invariants reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/04-invariants-validation-boundaries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/04-invariants-validation-boundaries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/04-invariants-validation-boundaries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/04-invariants-validation-boundaries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: bugs)

Reference: bugs
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 05) ROI=4.4 impact=6 confidence=0.78 effort=2 horizon=NearTerm category=caching/state reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/05-caching-state-state-artifacts-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/05-caching-state-state-artifacts-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/05-caching-state-state-artifacts-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/05-caching-state-state-artifacts-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 06) ROI=4.2 impact=6 confidence=0.75 effort=2 horizon=NearTerm category=caching/state reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/06-caching-state-cache-keys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/06-caching-state-cache-keys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/06-caching-state-cache-keys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/06-caching-state-cache-keys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: bugs)

Reference: bugs
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 07) ROI=4.3 impact=6 confidence=0.70 effort=2 horizon=MidTerm category=background jobs reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/07-background-jobs-job-model-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/07-background-jobs-job-model-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/07-background-jobs-job-model-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/07-background-jobs-job-model-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 08) ROI=4.0 impact=6 confidence=0.68 effort=3 horizon=MidTerm category=background jobs reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/08-background-jobs-queue-failure-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/08-background-jobs-queue-failure-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/08-background-jobs-queue-failure-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/08-background-jobs-queue-failure-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: bugs)

Reference: bugs
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 09) ROI=4.7 impact=7 confidence=0.76 effort=1 horizon=Immediate category=observability reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/09-observability-logging-metrics-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/09-observability-logging-metrics-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/09-observability-logging-metrics-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/09-observability-logging-metrics-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 10) ROI=4.5 impact=7 confidence=0.74 effort=2 horizon=Immediate category=observability reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/10-observability-tracing-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/10-observability-tracing-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/10-observability-tracing-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/10-observability-tracing-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: bugs)

Reference: bugs
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 11) ROI=4.1 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=permissions reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/11-permissions-authz-gaps-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/11-permissions-authz-gaps-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/11-permissions-authz-gaps-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/11-permissions-authz-gaps-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 12) ROI=3.9 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=permissions reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/12-permissions-secrets-config-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/12-permissions-secrets-config-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/12-permissions-secrets-config-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/12-permissions-secrets-config-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: bugs)

Reference: bugs
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 13) ROI=3.8 impact=6 confidence=0.66 effort=3 horizon=MidTerm category=migrations reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/13-migrations-schema-migrations-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/13-migrations-schema-migrations-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/13-migrations-schema-migrations-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/13-migrations-schema-migrations-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 14) ROI=3.7 impact=6 confidence=0.64 effort=3 horizon=MidTerm category=migrations reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/14-migrations-backfill-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/14-migrations-backfill-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/14-migrations-backfill-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/14-migrations-backfill-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: bugs)

Reference: bugs
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 15) ROI=4.6 impact=6 confidence=0.74 effort=1 horizon=Immediate category=UX flows reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/15-ux-flows-user-journeys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/15-ux-flows-user-journeys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/15-ux-flows-user-journeys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/15-ux-flows-user-journeys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 16) ROI=4.3 impact=6 confidence=0.72 effort=2 horizon=Immediate category=UX flows reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/16-ux-flows-edge-cases-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/16-ux-flows-edge-cases-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/16-ux-flows-edge-cases-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/16-ux-flows-edge-cases-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: bugs)

Reference: bugs
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 17) ROI=4.9 impact=7 confidence=0.78 effort=1 horizon=Immediate category=failure modes reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/17-failure-modes-timeouts-retries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/17-failure-modes-timeouts-retries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/17-failure-modes-timeouts-retries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/17-failure-modes-timeouts-retries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 18) ROI=4.4 impact=7 confidence=0.74 effort=2 horizon=Immediate category=failure modes reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/18-failure-modes-rollback-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/18-failure-modes-rollback-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/18-failure-modes-rollback-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/18-failure-modes-rollback-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: bugs)

Reference: bugs
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 19) ROI=4.0 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=feature flags reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/19-feature-flags-flag-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/19-feature-flags-flag-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/19-feature-flags-flag-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/19-feature-flags-flag-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 20) ROI=3.8 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=feature flags reference=bugs

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/bugs/Parent Ticket Summary.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'bugs'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/20-feature-flags-compat-rollout-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/20-feature-flags-compat-rollout-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/20-feature-flags-compat-rollout-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/bugs/20-feature-flags-compat-rollout-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: bugs)

Reference: bugs
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


```

## Coverage check
- contracts/interfaces: OK
- invariants: OK
- caching/state: OK
- background jobs: OK
- observability: OK
- permissions: OK
- migrations: OK
- UX flows: OK
- failure modes: OK
- feature flags: OK


--- docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/packs/lm-studio.md ---
# Oracle Pack — Unknown (Grouped Tickets Stage 1 — Direct Attach)

## Parsed args
- codebase_name: Unknown
- out_dir: docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio
- oracle_cmd: oracle
- oracle_flags: --files-report
- extra_files: 
- ticket_root: .tickets
- ticket_glob: **/*.md
- ticket_paths: .tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md
- ticket_max_files: 3
- group_name: lm-studio
- group_slug: lm-studio
- mode: tickets-grouped-direct

Notes (contract):
- Exactly one fenced `bash` block in this document.
- No other ``` fences anywhere.
- Exactly 20 steps, numbered 01..20 in order.
- Step header: `# NN) ROI=... impact=... confidence=... effort=... horizon=... category=... reference=...`
- Every step includes: `--write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/NN-<slug>.md"` (double quotes required).
- Steps must be self-contained and must not rely on shell variables created in previous steps.
- Each step must attach tickets directly (no `_tickets_bundle.md` dependency).
- Pack ends with a Coverage check section listing all 10 categories as OK or Missing(<step ids>).

```bash
set -euo pipefail

mkdir -p "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio"

# 01) ROI=4.8 impact=6 confidence=0.80 effort=1 horizon=Immediate category=contracts/interfaces reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/01-contracts-interfaces-ticket-surface-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/01-contracts-interfaces-ticket-surface-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/01-contracts-interfaces-ticket-surface-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/01-contracts-interfaces-ticket-surface-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 02) ROI=4.6 impact=6 confidence=0.78 effort=1 horizon=Immediate category=contracts/interfaces reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/02-contracts-interfaces-integration-points-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/02-contracts-interfaces-integration-points-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/02-contracts-interfaces-integration-points-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/02-contracts-interfaces-integration-points-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 03) ROI=5.1 impact=7 confidence=0.74 effort=1 horizon=NearTerm category=invariants reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/03-invariants-invariant-map-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/03-invariants-invariant-map-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/03-invariants-invariant-map-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/03-invariants-invariant-map-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 04) ROI=5.0 impact=7 confidence=0.72 effort=2 horizon=NearTerm category=invariants reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/04-invariants-validation-boundaries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/04-invariants-validation-boundaries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/04-invariants-validation-boundaries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/04-invariants-validation-boundaries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 05) ROI=4.4 impact=6 confidence=0.78 effort=2 horizon=NearTerm category=caching/state reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/05-caching-state-state-artifacts-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/05-caching-state-state-artifacts-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/05-caching-state-state-artifacts-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/05-caching-state-state-artifacts-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 06) ROI=4.2 impact=6 confidence=0.75 effort=2 horizon=NearTerm category=caching/state reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/06-caching-state-cache-keys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/06-caching-state-cache-keys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/06-caching-state-cache-keys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/06-caching-state-cache-keys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 07) ROI=4.3 impact=6 confidence=0.70 effort=2 horizon=MidTerm category=background jobs reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/07-background-jobs-job-model-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/07-background-jobs-job-model-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/07-background-jobs-job-model-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/07-background-jobs-job-model-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 08) ROI=4.0 impact=6 confidence=0.68 effort=3 horizon=MidTerm category=background jobs reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/08-background-jobs-queue-failure-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/08-background-jobs-queue-failure-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/08-background-jobs-queue-failure-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/08-background-jobs-queue-failure-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 09) ROI=4.7 impact=7 confidence=0.76 effort=1 horizon=Immediate category=observability reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/09-observability-logging-metrics-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/09-observability-logging-metrics-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/09-observability-logging-metrics-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/09-observability-logging-metrics-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 10) ROI=4.5 impact=7 confidence=0.74 effort=2 horizon=Immediate category=observability reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/10-observability-tracing-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/10-observability-tracing-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/10-observability-tracing-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/10-observability-tracing-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 11) ROI=4.1 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=permissions reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/11-permissions-authz-gaps-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/11-permissions-authz-gaps-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/11-permissions-authz-gaps-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/11-permissions-authz-gaps-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 12) ROI=3.9 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=permissions reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/12-permissions-secrets-config-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/12-permissions-secrets-config-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/12-permissions-secrets-config-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/12-permissions-secrets-config-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 13) ROI=3.8 impact=6 confidence=0.66 effort=3 horizon=MidTerm category=migrations reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/13-migrations-schema-migrations-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/13-migrations-schema-migrations-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/13-migrations-schema-migrations-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/13-migrations-schema-migrations-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 14) ROI=3.7 impact=6 confidence=0.64 effort=3 horizon=MidTerm category=migrations reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/14-migrations-backfill-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/14-migrations-backfill-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/14-migrations-backfill-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/14-migrations-backfill-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 15) ROI=4.6 impact=6 confidence=0.74 effort=1 horizon=Immediate category=UX flows reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/15-ux-flows-user-journeys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/15-ux-flows-user-journeys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/15-ux-flows-user-journeys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/15-ux-flows-user-journeys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 16) ROI=4.3 impact=6 confidence=0.72 effort=2 horizon=Immediate category=UX flows reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/16-ux-flows-edge-cases-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/16-ux-flows-edge-cases-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/16-ux-flows-edge-cases-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/16-ux-flows-edge-cases-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 17) ROI=4.9 impact=7 confidence=0.78 effort=1 horizon=Immediate category=failure modes reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/17-failure-modes-timeouts-retries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/17-failure-modes-timeouts-retries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/17-failure-modes-timeouts-retries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/17-failure-modes-timeouts-retries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 18) ROI=4.4 impact=7 confidence=0.74 effort=2 horizon=Immediate category=failure modes reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/18-failure-modes-rollback-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/18-failure-modes-rollback-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/18-failure-modes-rollback-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/18-failure-modes-rollback-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 19) ROI=4.0 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=feature flags reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/19-feature-flags-flag-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/19-feature-flags-flag-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/19-feature-flags-flag-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/19-feature-flags-flag-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 20) ROI=3.8 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=feature flags reference=lm-studio

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/lm-studio/LM Studio Sampling Params.md,.tickets/lm-studio/LM-Studio-Model-Selection.md,.tickets/lm-studio/LMStudioAdapter Enhancement.md".strip()
MAX = int("3")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'lm-studio'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/20-feature-flags-compat-rollout-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/20-feature-flags-compat-rollout-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/20-feature-flags-compat-rollout-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/lm-studio/20-feature-flags-compat-rollout-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: lm-studio)

Reference: lm-studio
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


```

## Coverage check
- contracts/interfaces: OK
- invariants: OK
- caching/state: OK
- background jobs: OK
- observability: OK
- permissions: OK
- migrations: OK
- UX flows: OK
- failure modes: OK
- feature flags: OK


--- docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/packs/mcp.md ---
# Oracle Pack — Unknown (Grouped Tickets Stage 1 — Direct Attach)

## Parsed args
- codebase_name: Unknown
- out_dir: docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp
- oracle_cmd: oracle
- oracle_flags: --files-report
- extra_files: 
- ticket_root: .tickets
- ticket_glob: **/*.md
- ticket_paths: .tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md
- ticket_max_files: 5
- group_name: mcp
- group_slug: mcp
- mode: tickets-grouped-direct

Notes (contract):
- Exactly one fenced `bash` block in this document.
- No other ``` fences anywhere.
- Exactly 20 steps, numbered 01..20 in order.
- Step header: `# NN) ROI=... impact=... confidence=... effort=... horizon=... category=... reference=...`
- Every step includes: `--write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/NN-<slug>.md"` (double quotes required).
- Steps must be self-contained and must not rely on shell variables created in previous steps.
- Each step must attach tickets directly (no `_tickets_bundle.md` dependency).
- Pack ends with a Coverage check section listing all 10 categories as OK or Missing(<step ids>).

```bash
set -euo pipefail

mkdir -p "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp"

# 01) ROI=4.8 impact=6 confidence=0.80 effort=1 horizon=Immediate category=contracts/interfaces reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/01-contracts-interfaces-ticket-surface-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/01-contracts-interfaces-ticket-surface-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/01-contracts-interfaces-ticket-surface-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/01-contracts-interfaces-ticket-surface-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 02) ROI=4.6 impact=6 confidence=0.78 effort=1 horizon=Immediate category=contracts/interfaces reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/02-contracts-interfaces-integration-points-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/02-contracts-interfaces-integration-points-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/02-contracts-interfaces-integration-points-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/02-contracts-interfaces-integration-points-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: mcp)

Reference: mcp
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 03) ROI=5.1 impact=7 confidence=0.74 effort=1 horizon=NearTerm category=invariants reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/03-invariants-invariant-map-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/03-invariants-invariant-map-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/03-invariants-invariant-map-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/03-invariants-invariant-map-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 04) ROI=5.0 impact=7 confidence=0.72 effort=2 horizon=NearTerm category=invariants reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/04-invariants-validation-boundaries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/04-invariants-validation-boundaries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/04-invariants-validation-boundaries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/04-invariants-validation-boundaries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: mcp)

Reference: mcp
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 05) ROI=4.4 impact=6 confidence=0.78 effort=2 horizon=NearTerm category=caching/state reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/05-caching-state-state-artifacts-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/05-caching-state-state-artifacts-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/05-caching-state-state-artifacts-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/05-caching-state-state-artifacts-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 06) ROI=4.2 impact=6 confidence=0.75 effort=2 horizon=NearTerm category=caching/state reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/06-caching-state-cache-keys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/06-caching-state-cache-keys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/06-caching-state-cache-keys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/06-caching-state-cache-keys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: mcp)

Reference: mcp
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 07) ROI=4.3 impact=6 confidence=0.70 effort=2 horizon=MidTerm category=background jobs reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/07-background-jobs-job-model-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/07-background-jobs-job-model-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/07-background-jobs-job-model-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/07-background-jobs-job-model-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 08) ROI=4.0 impact=6 confidence=0.68 effort=3 horizon=MidTerm category=background jobs reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/08-background-jobs-queue-failure-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/08-background-jobs-queue-failure-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/08-background-jobs-queue-failure-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/08-background-jobs-queue-failure-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: mcp)

Reference: mcp
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 09) ROI=4.7 impact=7 confidence=0.76 effort=1 horizon=Immediate category=observability reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/09-observability-logging-metrics-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/09-observability-logging-metrics-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/09-observability-logging-metrics-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/09-observability-logging-metrics-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 10) ROI=4.5 impact=7 confidence=0.74 effort=2 horizon=Immediate category=observability reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/10-observability-tracing-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/10-observability-tracing-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/10-observability-tracing-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/10-observability-tracing-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: mcp)

Reference: mcp
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 11) ROI=4.1 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=permissions reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/11-permissions-authz-gaps-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/11-permissions-authz-gaps-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/11-permissions-authz-gaps-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/11-permissions-authz-gaps-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 12) ROI=3.9 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=permissions reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/12-permissions-secrets-config-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/12-permissions-secrets-config-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/12-permissions-secrets-config-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/12-permissions-secrets-config-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: mcp)

Reference: mcp
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 13) ROI=3.8 impact=6 confidence=0.66 effort=3 horizon=MidTerm category=migrations reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/13-migrations-schema-migrations-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/13-migrations-schema-migrations-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/13-migrations-schema-migrations-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/13-migrations-schema-migrations-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 14) ROI=3.7 impact=6 confidence=0.64 effort=3 horizon=MidTerm category=migrations reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/14-migrations-backfill-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/14-migrations-backfill-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/14-migrations-backfill-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/14-migrations-backfill-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: mcp)

Reference: mcp
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 15) ROI=4.6 impact=6 confidence=0.74 effort=1 horizon=Immediate category=UX flows reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/15-ux-flows-user-journeys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/15-ux-flows-user-journeys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/15-ux-flows-user-journeys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/15-ux-flows-user-journeys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 16) ROI=4.3 impact=6 confidence=0.72 effort=2 horizon=Immediate category=UX flows reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/16-ux-flows-edge-cases-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/16-ux-flows-edge-cases-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/16-ux-flows-edge-cases-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/16-ux-flows-edge-cases-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: mcp)

Reference: mcp
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 17) ROI=4.9 impact=7 confidence=0.78 effort=1 horizon=Immediate category=failure modes reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/17-failure-modes-timeouts-retries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/17-failure-modes-timeouts-retries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/17-failure-modes-timeouts-retries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/17-failure-modes-timeouts-retries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 18) ROI=4.4 impact=7 confidence=0.74 effort=2 horizon=Immediate category=failure modes reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/18-failure-modes-rollback-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/18-failure-modes-rollback-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/18-failure-modes-rollback-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/18-failure-modes-rollback-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: mcp)

Reference: mcp
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 19) ROI=4.0 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=feature flags reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/19-feature-flags-flag-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/19-feature-flags-flag-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/19-feature-flags-flag-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/19-feature-flags-flag-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 20) ROI=3.8 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=feature flags reference=mcp

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/mcp/Expose MCP Server for Promptbench.md,.tickets/mcp/Expose promptbench as MCP.md,.tickets/mcp/MCP server implementation.md,.tickets/mcp/mcp-goal.md,.tickets/mcp/promptbench_mcp_server_design_implementation.md".strip()
MAX = int("5")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'mcp'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/20-feature-flags-compat-rollout-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/20-feature-flags-compat-rollout-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/20-feature-flags-compat-rollout-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/mcp/20-feature-flags-compat-rollout-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: mcp)

Reference: mcp
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


```

## Coverage check
- contracts/interfaces: OK
- invariants: OK
- caching/state: OK
- background jobs: OK
- observability: OK
- permissions: OK
- migrations: OK
- UX flows: OK
- failure modes: OK
- feature flags: OK


--- docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/packs/misc.md ---
# Oracle Pack — Unknown (Grouped Tickets Stage 1 — Direct Attach)

## Parsed args
- codebase_name: Unknown
- out_dir: docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc
- oracle_cmd: oracle
- oracle_flags: --files-report
- extra_files: 
- ticket_root: .tickets
- ticket_glob: **/*.md
- ticket_paths: .tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md
- ticket_max_files: 6
- group_name: misc
- group_slug: misc
- mode: tickets-grouped-direct

Notes (contract):
- Exactly one fenced `bash` block in this document.
- No other ``` fences anywhere.
- Exactly 20 steps, numbered 01..20 in order.
- Step header: `# NN) ROI=... impact=... confidence=... effort=... horizon=... category=... reference=...`
- Every step includes: `--write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/NN-<slug>.md"` (double quotes required).
- Steps must be self-contained and must not rely on shell variables created in previous steps.
- Each step must attach tickets directly (no `_tickets_bundle.md` dependency).
- Pack ends with a Coverage check section listing all 10 categories as OK or Missing(<step ids>).

```bash
set -euo pipefail

mkdir -p "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc"

# 01) ROI=4.8 impact=6 confidence=0.80 effort=1 horizon=Immediate category=contracts/interfaces reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/01-contracts-interfaces-ticket-surface-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/01-contracts-interfaces-ticket-surface-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/01-contracts-interfaces-ticket-surface-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/01-contracts-interfaces-ticket-surface-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 02) ROI=4.6 impact=6 confidence=0.78 effort=1 horizon=Immediate category=contracts/interfaces reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/02-contracts-interfaces-integration-points-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/02-contracts-interfaces-integration-points-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/02-contracts-interfaces-integration-points-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/02-contracts-interfaces-integration-points-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: misc)

Reference: misc
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 03) ROI=5.1 impact=7 confidence=0.74 effort=1 horizon=NearTerm category=invariants reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/03-invariants-invariant-map-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/03-invariants-invariant-map-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/03-invariants-invariant-map-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/03-invariants-invariant-map-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 04) ROI=5.0 impact=7 confidence=0.72 effort=2 horizon=NearTerm category=invariants reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/04-invariants-validation-boundaries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/04-invariants-validation-boundaries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/04-invariants-validation-boundaries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/04-invariants-validation-boundaries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: misc)

Reference: misc
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 05) ROI=4.4 impact=6 confidence=0.78 effort=2 horizon=NearTerm category=caching/state reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/05-caching-state-state-artifacts-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/05-caching-state-state-artifacts-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/05-caching-state-state-artifacts-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/05-caching-state-state-artifacts-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 06) ROI=4.2 impact=6 confidence=0.75 effort=2 horizon=NearTerm category=caching/state reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/06-caching-state-cache-keys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/06-caching-state-cache-keys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/06-caching-state-cache-keys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/06-caching-state-cache-keys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: misc)

Reference: misc
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 07) ROI=4.3 impact=6 confidence=0.70 effort=2 horizon=MidTerm category=background jobs reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/07-background-jobs-job-model-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/07-background-jobs-job-model-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/07-background-jobs-job-model-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/07-background-jobs-job-model-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 08) ROI=4.0 impact=6 confidence=0.68 effort=3 horizon=MidTerm category=background jobs reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/08-background-jobs-queue-failure-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/08-background-jobs-queue-failure-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/08-background-jobs-queue-failure-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/08-background-jobs-queue-failure-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: misc)

Reference: misc
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 09) ROI=4.7 impact=7 confidence=0.76 effort=1 horizon=Immediate category=observability reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/09-observability-logging-metrics-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/09-observability-logging-metrics-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/09-observability-logging-metrics-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/09-observability-logging-metrics-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 10) ROI=4.5 impact=7 confidence=0.74 effort=2 horizon=Immediate category=observability reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/10-observability-tracing-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/10-observability-tracing-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/10-observability-tracing-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/10-observability-tracing-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: misc)

Reference: misc
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 11) ROI=4.1 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=permissions reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/11-permissions-authz-gaps-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/11-permissions-authz-gaps-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/11-permissions-authz-gaps-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/11-permissions-authz-gaps-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 12) ROI=3.9 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=permissions reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/12-permissions-secrets-config-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/12-permissions-secrets-config-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/12-permissions-secrets-config-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/12-permissions-secrets-config-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: misc)

Reference: misc
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 13) ROI=3.8 impact=6 confidence=0.66 effort=3 horizon=MidTerm category=migrations reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/13-migrations-schema-migrations-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/13-migrations-schema-migrations-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/13-migrations-schema-migrations-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/13-migrations-schema-migrations-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 14) ROI=3.7 impact=6 confidence=0.64 effort=3 horizon=MidTerm category=migrations reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/14-migrations-backfill-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/14-migrations-backfill-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/14-migrations-backfill-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/14-migrations-backfill-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: misc)

Reference: misc
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 15) ROI=4.6 impact=6 confidence=0.74 effort=1 horizon=Immediate category=UX flows reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/15-ux-flows-user-journeys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/15-ux-flows-user-journeys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/15-ux-flows-user-journeys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/15-ux-flows-user-journeys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 16) ROI=4.3 impact=6 confidence=0.72 effort=2 horizon=Immediate category=UX flows reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/16-ux-flows-edge-cases-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/16-ux-flows-edge-cases-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/16-ux-flows-edge-cases-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/16-ux-flows-edge-cases-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: misc)

Reference: misc
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 17) ROI=4.9 impact=7 confidence=0.78 effort=1 horizon=Immediate category=failure modes reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/17-failure-modes-timeouts-retries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/17-failure-modes-timeouts-retries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/17-failure-modes-timeouts-retries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/17-failure-modes-timeouts-retries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 18) ROI=4.4 impact=7 confidence=0.74 effort=2 horizon=Immediate category=failure modes reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/18-failure-modes-rollback-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/18-failure-modes-rollback-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/18-failure-modes-rollback-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/18-failure-modes-rollback-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: misc)

Reference: misc
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 19) ROI=4.0 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=feature flags reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/19-feature-flags-flag-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/19-feature-flags-flag-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/19-feature-flags-flag-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/19-feature-flags-flag-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 20) ROI=3.8 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=feature flags reference=misc

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Progress View Issue.md,.tickets/Prompt-Bench Pipeline Enhancement.md,.tickets/PromptBench LM Studio Integration.md,.tickets/Promptbench Composition Modes.md,.tickets/Skill Router Improvement Plan.md,.tickets/Web-search for promptbench skills.md".strip()
MAX = int("6")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'misc'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/20-feature-flags-compat-rollout-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/20-feature-flags-compat-rollout-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/20-feature-flags-compat-rollout-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/misc/20-feature-flags-compat-rollout-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: misc)

Reference: misc
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


```

## Coverage check
- contracts/interfaces: OK
- invariants: OK
- caching/state: OK
- background jobs: OK
- observability: OK
- permissions: OK
- migrations: OK
- UX flows: OK
- failure modes: OK
- feature flags: OK


--- docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/packs/raw-exports.md ---
# Oracle Pack — Unknown (Grouped Tickets Stage 1 — Direct Attach)

## Parsed args
- codebase_name: Unknown
- out_dir: docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports
- oracle_cmd: oracle
- oracle_flags: --files-report
- extra_files: 
- ticket_root: .tickets
- ticket_glob: **/*.md
- ticket_paths: .tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md
- ticket_max_files: 4
- group_name: raw-exports
- group_slug: raw-exports
- mode: tickets-grouped-direct

Notes (contract):
- Exactly one fenced `bash` block in this document.
- No other ``` fences anywhere.
- Exactly 20 steps, numbered 01..20 in order.
- Step header: `# NN) ROI=... impact=... confidence=... effort=... horizon=... category=... reference=...`
- Every step includes: `--write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/NN-<slug>.md"` (double quotes required).
- Steps must be self-contained and must not rely on shell variables created in previous steps.
- Each step must attach tickets directly (no `_tickets_bundle.md` dependency).
- Pack ends with a Coverage check section listing all 10 categories as OK or Missing(<step ids>).

```bash
set -euo pipefail

mkdir -p "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports"

# 01) ROI=4.8 impact=6 confidence=0.80 effort=1 horizon=Immediate category=contracts/interfaces reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/01-contracts-interfaces-ticket-surface-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/01-contracts-interfaces-ticket-surface-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/01-contracts-interfaces-ticket-surface-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/01-contracts-interfaces-ticket-surface-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 02) ROI=4.6 impact=6 confidence=0.78 effort=1 horizon=Immediate category=contracts/interfaces reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/02-contracts-interfaces-integration-points-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/02-contracts-interfaces-integration-points-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/02-contracts-interfaces-integration-points-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/02-contracts-interfaces-integration-points-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 03) ROI=5.1 impact=7 confidence=0.74 effort=1 horizon=NearTerm category=invariants reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/03-invariants-invariant-map-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/03-invariants-invariant-map-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/03-invariants-invariant-map-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/03-invariants-invariant-map-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 04) ROI=5.0 impact=7 confidence=0.72 effort=2 horizon=NearTerm category=invariants reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/04-invariants-validation-boundaries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/04-invariants-validation-boundaries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/04-invariants-validation-boundaries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/04-invariants-validation-boundaries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 05) ROI=4.4 impact=6 confidence=0.78 effort=2 horizon=NearTerm category=caching/state reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/05-caching-state-state-artifacts-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/05-caching-state-state-artifacts-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/05-caching-state-state-artifacts-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/05-caching-state-state-artifacts-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 06) ROI=4.2 impact=6 confidence=0.75 effort=2 horizon=NearTerm category=caching/state reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/06-caching-state-cache-keys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/06-caching-state-cache-keys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/06-caching-state-cache-keys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/06-caching-state-cache-keys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 07) ROI=4.3 impact=6 confidence=0.70 effort=2 horizon=MidTerm category=background jobs reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/07-background-jobs-job-model-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/07-background-jobs-job-model-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/07-background-jobs-job-model-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/07-background-jobs-job-model-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 08) ROI=4.0 impact=6 confidence=0.68 effort=3 horizon=MidTerm category=background jobs reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/08-background-jobs-queue-failure-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/08-background-jobs-queue-failure-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/08-background-jobs-queue-failure-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/08-background-jobs-queue-failure-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 09) ROI=4.7 impact=7 confidence=0.76 effort=1 horizon=Immediate category=observability reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/09-observability-logging-metrics-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/09-observability-logging-metrics-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/09-observability-logging-metrics-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/09-observability-logging-metrics-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 10) ROI=4.5 impact=7 confidence=0.74 effort=2 horizon=Immediate category=observability reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/10-observability-tracing-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/10-observability-tracing-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/10-observability-tracing-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/10-observability-tracing-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 11) ROI=4.1 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=permissions reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/11-permissions-authz-gaps-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/11-permissions-authz-gaps-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/11-permissions-authz-gaps-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/11-permissions-authz-gaps-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 12) ROI=3.9 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=permissions reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/12-permissions-secrets-config-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/12-permissions-secrets-config-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/12-permissions-secrets-config-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/12-permissions-secrets-config-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 13) ROI=3.8 impact=6 confidence=0.66 effort=3 horizon=MidTerm category=migrations reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/13-migrations-schema-migrations-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/13-migrations-schema-migrations-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/13-migrations-schema-migrations-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/13-migrations-schema-migrations-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 14) ROI=3.7 impact=6 confidence=0.64 effort=3 horizon=MidTerm category=migrations reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/14-migrations-backfill-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/14-migrations-backfill-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/14-migrations-backfill-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/14-migrations-backfill-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 15) ROI=4.6 impact=6 confidence=0.74 effort=1 horizon=Immediate category=UX flows reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/15-ux-flows-user-journeys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/15-ux-flows-user-journeys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/15-ux-flows-user-journeys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/15-ux-flows-user-journeys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 16) ROI=4.3 impact=6 confidence=0.72 effort=2 horizon=Immediate category=UX flows reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/16-ux-flows-edge-cases-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/16-ux-flows-edge-cases-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/16-ux-flows-edge-cases-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/16-ux-flows-edge-cases-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 17) ROI=4.9 impact=7 confidence=0.78 effort=1 horizon=Immediate category=failure modes reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/17-failure-modes-timeouts-retries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/17-failure-modes-timeouts-retries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/17-failure-modes-timeouts-retries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/17-failure-modes-timeouts-retries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 18) ROI=4.4 impact=7 confidence=0.74 effort=2 horizon=Immediate category=failure modes reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/18-failure-modes-rollback-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/18-failure-modes-rollback-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/18-failure-modes-rollback-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/18-failure-modes-rollback-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 19) ROI=4.0 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=feature flags reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/19-feature-flags-flag-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/19-feature-flags-flag-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/19-feature-flags-flag-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/19-feature-flags-flag-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 20) ROI=3.8 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=feature flags reference=raw-exports

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/raw-exports/LM Studio API Error.md,.tickets/raw-exports/Progress View Issue.md,.tickets/raw-exports/Promptbench vs LM Studio (1).md,.tickets/raw-exports/Skill Router Explanation.md".strip()
MAX = int("4")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'raw-exports'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/20-feature-flags-compat-rollout-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/20-feature-flags-compat-rollout-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/20-feature-flags-compat-rollout-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/raw-exports/20-feature-flags-compat-rollout-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: raw-exports)

Reference: raw-exports
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


```

## Coverage check
- contracts/interfaces: OK
- invariants: OK
- caching/state: OK
- background jobs: OK
- observability: OK
- permissions: OK
- migrations: OK
- UX flows: OK
- failure modes: OK
- feature flags: OK


--- docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/packs/standardize-promptbench-workspace.md ---
# Oracle Pack — Unknown (Grouped Tickets Stage 1 — Direct Attach)

## Parsed args
- codebase_name: Unknown
- out_dir: docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace
- oracle_cmd: oracle
- oracle_flags: --files-report
- extra_files: 
- ticket_root: .tickets
- ticket_glob: **/*.md
- ticket_paths: .tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md
- ticket_max_files: 1
- group_name: Standardize_promptbench-workspace
- group_slug: standardize-promptbench-workspace
- mode: tickets-grouped-direct

Notes (contract):
- Exactly one fenced `bash` block in this document.
- No other ``` fences anywhere.
- Exactly 20 steps, numbered 01..20 in order.
- Step header: `# NN) ROI=... impact=... confidence=... effort=... horizon=... category=... reference=...`
- Every step includes: `--write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/NN-<slug>.md"` (double quotes required).
- Steps must be self-contained and must not rely on shell variables created in previous steps.
- Each step must attach tickets directly (no `_tickets_bundle.md` dependency).
- Pack ends with a Coverage check section listing all 10 categories as OK or Missing(<step ids>).

```bash
set -euo pipefail

mkdir -p "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace"

# 01) ROI=4.8 impact=6 confidence=0.80 effort=1 horizon=Immediate category=contracts/interfaces reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/01-contracts-interfaces-ticket-surface-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/01-contracts-interfaces-ticket-surface-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/01-contracts-interfaces-ticket-surface-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/01-contracts-interfaces-ticket-surface-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #01  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.8 (impact=6, confidence=0.80, effort=1)

Question:
Using the attached tickets as the primary context, list the public surface changes implied by the tickets (CLI/TUI/API/interfaces/contracts); call out backwards-compat constraints.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 02) ROI=4.6 impact=6 confidence=0.78 effort=1 horizon=Immediate category=contracts/interfaces reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/02-contracts-interfaces-integration-points-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/02-contracts-interfaces-integration-points-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/02-contracts-interfaces-integration-points-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/02-contracts-interfaces-integration-points-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #02  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: contracts/interfaces
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, identify external integrations implied by the tickets; required config/contract changes; failure/timeout behavior; minimal compat-safe rollout.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 03) ROI=5.1 impact=7 confidence=0.74 effort=1 horizon=NearTerm category=invariants reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/03-invariants-invariant-map-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/03-invariants-invariant-map-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/03-invariants-invariant-map-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/03-invariants-invariant-map-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #03  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.1 (impact=7, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, extract system invariants implied by tickets (inputs/outputs, pack schema rules, step execution rules) and where to enforce them.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 04) ROI=5.0 impact=7 confidence=0.72 effort=2 horizon=NearTerm category=invariants reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/04-invariants-validation-boundaries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/04-invariants-validation-boundaries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/04-invariants-validation-boundaries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/04-invariants-validation-boundaries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #04  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: invariants
Horizon: NearTerm
ROI: 5.0 (impact=7, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify validation boundaries that must exist (ticket parsing, pack generation, pack validation); propose minimal validation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 05) ROI=4.4 impact=6 confidence=0.78 effort=2 horizon=NearTerm category=caching/state reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/05-caching-state-state-artifacts-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/05-caching-state-state-artifacts-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/05-caching-state-state-artifacts-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/05-caching-state-state-artifacts-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #05  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.4 (impact=6, confidence=0.78, effort=2)

Question:
Using the attached tickets as the primary context, identify state/artifacts that must be produced and preserved; schema/format expectations; stability/back-compat requirements.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 06) ROI=4.2 impact=6 confidence=0.75 effort=2 horizon=NearTerm category=caching/state reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/06-caching-state-cache-keys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/06-caching-state-cache-keys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/06-caching-state-cache-keys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/06-caching-state-cache-keys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #06  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: caching/state
Horizon: NearTerm
ROI: 4.2 (impact=6, confidence=0.75, effort=2)

Question:
Using the attached tickets as the primary context, identify any caching opportunities/risks (discovery caches, pack outputs, oracle outputs); define cache keys, invalidation, and correctness risks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 07) ROI=4.3 impact=6 confidence=0.70 effort=2 horizon=MidTerm category=background jobs reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/07-background-jobs-job-model-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/07-background-jobs-job-model-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/07-background-jobs-job-model-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/07-background-jobs-job-model-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #07  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.3 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify any background/async work implied (jobs, queues, long-running operations); define responsibilities and interfaces.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 08) ROI=4.0 impact=6 confidence=0.68 effort=3 horizon=MidTerm category=background jobs reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/08-background-jobs-queue-failure-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/08-background-jobs-queue-failure-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/08-background-jobs-queue-failure-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/08-background-jobs-queue-failure-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #08  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: background jobs
Horizon: MidTerm
ROI: 4.0 (impact=6, confidence=0.68, effort=3)

Question:
Using the attached tickets as the primary context, define how background failures are handled (retries, idempotency, poison messages); define observability hooks.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 09) ROI=4.7 impact=7 confidence=0.76 effort=1 horizon=Immediate category=observability reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/09-observability-logging-metrics-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/09-observability-logging-metrics-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/09-observability-logging-metrics-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/09-observability-logging-metrics-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #09  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.7 (impact=7, confidence=0.76, effort=1)

Question:
Using the attached tickets as the primary context, define what logging/metrics must exist to debug pack generation + step execution; propose minimal instrumentation plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 10) ROI=4.5 impact=7 confidence=0.74 effort=2 horizon=Immediate category=observability reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/10-observability-tracing-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/10-observability-tracing-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/10-observability-tracing-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/10-observability-tracing-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #10  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: observability
Horizon: Immediate
ROI: 4.5 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define tracing/correlation strategy across pack steps and downstream tools; identify required IDs and propagation.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 11) ROI=4.1 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=permissions reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/11-permissions-authz-gaps-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/11-permissions-authz-gaps-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/11-permissions-authz-gaps-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/11-permissions-authz-gaps-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #11  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 4.1 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, identify permission/authz boundaries implied by tickets (file access, command execution, network); propose safe defaults.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 12) ROI=3.9 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=permissions reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/12-permissions-secrets-config-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/12-permissions-secrets-config-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/12-permissions-secrets-config-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/12-permissions-secrets-config-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #12  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: permissions
Horizon: NearTerm
ROI: 3.9 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, identify secrets/config handling needs (API keys, tokens); propose secure config discovery and redaction.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 13) ROI=3.8 impact=6 confidence=0.66 effort=3 horizon=MidTerm category=migrations reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/13-migrations-schema-migrations-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/13-migrations-schema-migrations-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/13-migrations-schema-migrations-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/13-migrations-schema-migrations-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #13  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.8 (impact=6, confidence=0.66, effort=3)

Question:
Using the attached tickets as the primary context, identify any required migrations (schema/format/CLI flags); define migration strategy and compat approach.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 14) ROI=3.7 impact=6 confidence=0.64 effort=3 horizon=MidTerm category=migrations reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/14-migrations-backfill-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/14-migrations-backfill-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/14-migrations-backfill-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/14-migrations-backfill-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #14  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: migrations
Horizon: MidTerm
ROI: 3.7 (impact=6, confidence=0.64, effort=3)

Question:
Using the attached tickets as the primary context, define any needed backfill/one-time transforms; estimate risks; define verification plan.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 15) ROI=4.6 impact=6 confidence=0.74 effort=1 horizon=Immediate category=UX flows reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/15-ux-flows-user-journeys-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/15-ux-flows-user-journeys-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/15-ux-flows-user-journeys-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/15-ux-flows-user-journeys-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #15  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.6 (impact=6, confidence=0.74, effort=1)

Question:
Using the attached tickets as the primary context, identify UX/TUI workflows implied by tickets; define user journey states and expected outputs.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 16) ROI=4.3 impact=6 confidence=0.72 effort=2 horizon=Immediate category=UX flows reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/16-ux-flows-edge-cases-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/16-ux-flows-edge-cases-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/16-ux-flows-edge-cases-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/16-ux-flows-edge-cases-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #16  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: UX flows
Horizon: Immediate
ROI: 4.3 (impact=6, confidence=0.72, effort=2)

Question:
Using the attached tickets as the primary context, identify edge cases in UX flows (cancel, resume, partial runs); define minimal UX behavior.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 17) ROI=4.9 impact=7 confidence=0.78 effort=1 horizon=Immediate category=failure modes reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/17-failure-modes-timeouts-retries-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/17-failure-modes-timeouts-retries-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/17-failure-modes-timeouts-retries-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/17-failure-modes-timeouts-retries-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #17  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.9 (impact=7, confidence=0.78, effort=1)

Question:
Using the attached tickets as the primary context, define timeouts/retries behavior for external calls; define failure classification and operator actions.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 18) ROI=4.4 impact=7 confidence=0.74 effort=2 horizon=Immediate category=failure modes reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/18-failure-modes-rollback-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/18-failure-modes-rollback-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/18-failure-modes-rollback-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/18-failure-modes-rollback-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #18  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: failure modes
Horizon: Immediate
ROI: 4.4 (impact=7, confidence=0.74, effort=2)

Question:
Using the attached tickets as the primary context, define rollback plan for partial runs and how to preserve artifacts; define 'safe to re-run' semantics.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 19) ROI=4.0 impact=6 confidence=0.70 effort=2 horizon=NearTerm category=feature flags reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/19-feature-flags-flag-plan-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/19-feature-flags-flag-plan-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/19-feature-flags-flag-plan-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/19-feature-flags-flag-plan-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #19  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 4.0 (impact=6, confidence=0.70, effort=2)

Question:
Using the attached tickets as the primary context, define feature-flag strategy for rollout (scopes, defaults, telemetry); ensure compat for existing users.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


# 20) ROI=3.8 impact=6 confidence=0.68 effort=2 horizon=NearTerm category=feature flags reference=standardize-promptbench-workspace

# tickets attached directly (deterministic; self-contained)
mapfile -t __tickets < <(python3 - <<'PY'
from __future__ import annotations
from pathlib import Path
import fnmatch

TICKET_ROOT = ".tickets"
TICKET_GLOB = "**/*.md"
TICKET_PATHS = ".tickets/Standardize_promptbench-workspace/Standardize_.promptbench-workspace.md".strip()
MAX = int("1")


root = Path(TICKET_ROOT)

def read_gitignore(start: Path):
    cur = start.resolve()
    if cur.is_file():
        cur = cur.parent
    while True:
        p = cur / ".gitignore"
        if p.exists():
            lines = []
            for ln in p.read_text(encoding="utf-8", errors="replace").splitlines():
                s = ln.strip()
                if not s or s.startswith("#"):
                    continue
                lines.append(s)
            return cur, lines
        if cur.parent == cur:
            return start.resolve(), []
        cur = cur.parent


def gitignore_match(rel_posix: str, name: str, pattern: str) -> bool:
    neg = pattern.startswith("!")
    if neg:
        pattern = pattern[1:]
    if not pattern:
        return False

    anchored = pattern.startswith("/")
    if anchored:
        pattern = pattern.lstrip("/")

    dir_only = pattern.endswith("/")
    if dir_only:
        pattern = pattern.rstrip("/")

    if "/" not in pattern:
        if dir_only:
            return any(part == pattern for part in rel_posix.split("/"))
        return fnmatch.fnmatch(name, pattern)

    target = rel_posix
    if anchored:
        if dir_only:
            return target.startswith(pattern + "/")
        return fnmatch.fnmatch(target, pattern)
    if dir_only:
        return f"/{pattern}/" in f"/{target}/"
    return fnmatch.fnmatch(target, f"**/{pattern}") or fnmatch.fnmatch(target, pattern)


def is_gitignored(path: Path, git_root: Path, patterns: list[str]) -> bool:
    try:
        rel = path.resolve().relative_to(git_root)
    except Exception:
        rel = path
    rel_posix = rel.as_posix()
    name = rel_posix.split("/")[-1]
    ignored = False
    for pat in patterns:
        neg = pat.startswith("!")
        if gitignore_match(rel_posix, name, pat):
            ignored = not neg
    return ignored


git_root, git_patterns = read_gitignore(root)
def lex_sorted(ps):
    return sorted((str(p) for p in ps), key=lambda s: s)

if TICKET_PATHS:
    tickets = [Path(p.strip()) for p in TICKET_PATHS.split(",") if p.strip()]
else:
    tickets = list(root.glob(TICKET_GLOB)) if root.exists() else []

tickets = [Path(p) for p in lex_sorted(tickets)]
tickets = [p for p in tickets if not is_gitignored(p, git_root, git_patterns)]
if MAX and MAX > 0:
    tickets = tickets[:MAX]

for p in tickets:
    print(str(p))
PY
)

ticket_args=()
for p in "${__tickets[@]}"; do
  ticket_args+=(-f "$p")
done

if [ "${#ticket_args[@]}" -eq 0 ]; then
  echo "WARNING: no tickets resolved for group 'Standardize_promptbench-workspace'." >&2
fi

# extra_files appended literally (may be empty; may include -f/--file):
oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/20-feature-flags-compat-rollout-direct-answer.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Direct answer
Start with heading: Direct answer
Return only: Direct answer (1–10 bullets, evidence-cited)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/20-feature-flags-compat-rollout-risks-unknowns.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Risks/unknowns
Start with heading: Risks/unknowns
Return only: Risks/unknowns (bullets)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/20-feature-flags-compat-rollout-next-experiment.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Next smallest concrete experiment
Start with heading: Next smallest concrete experiment
Return only: Next smallest concrete experiment (exactly one action)
PROMPT
)"

oracle   --files-report   --write-output "docs/oracle-questions-2026-01-09-154207-run-2026-01-09-154207/standardize-promptbench-workspace/20-feature-flags-compat-rollout-missing-evidence.md"   "${ticket_args[@]}"      -p "$(cat <<'PROMPT'
Strategist question #20  (ticket-driven, group: Standardize_promptbench-workspace)

Reference: standardize-promptbench-workspace
Category: feature flags
Horizon: NearTerm
ROI: 3.8 (impact=6, confidence=0.68, effort=2)

Question:
Using the attached tickets as the primary context, define minimal compat-safe rollout plan and guardrails; include fallback behavior and monitoring gates.

Constraints: None
Non-goals: None

Answer format:
1) Direct answer (1–10 bullets, evidence-cited)
2) Risks/unknowns (bullets)
3) Next smallest concrete experiment (exactly one action)
4) If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.

Output section: Missing evidence
Start with heading: Missing evidence
Return only: If evidence is insufficient, name the exact missing file/path pattern(s) to attach next.
PROMPT
)"


```

## Coverage check
- contracts/interfaces: OK
- invariants: OK
- caching/state: OK
- background jobs: OK
- observability: OK
- permissions: OK
- migrations: OK
- UX flows: OK
- failure modes: OK
- feature flags: OK


--- examples/expected-output.md ---
```md
#1
[Spec]
SYSTEM: Conversation-to-Issue-Ticket Converter

Objective
- Convert an exported user↔assistant conversation into ONE cleaned, coherent issue ticket.
- Preserve all key details; remove noise; do NOT add new facts.

Input
- Exported conversation text with alternating “user” and “assistant” messages (any format: plain text/markdown/JSON-like; timestamps optional).

Non-negotiables
- Fidelity: Do not invent requirements, causes, decisions, timelines, metrics, links, or outcomes.
- Completeness: Retain all materially relevant details (goals, constraints, edge cases, decisions, rejected options, action items, dependencies, risks).
- No questions: Never ask for missing info. Use “Unknown” / “Not provided” where needed.
- De-duplication: Merge repeats; keep the clearest formulation.
- Conflicts: If contradictions exist, report both versions with attribution (per user / per assistant). Do not resolve by guessing.
- Terminology: Normalize names/terms to the most consistent wording present in the conversation.
- Traceability: For critical/ambiguous details, add attribution and optionally a short quote fragment (≤25 words).
- Security/privacy: Redact credentials and sensitive personal data; replace with “[REDACTED]” and note the redaction.

Process
1) Segment into: problem statement(s), context, requirements, constraints, environment, attempted fixes, errors, decisions, next steps.
2) Identify: primary issue; secondary issues (if any); stakeholders/owners (if stated); target system/component; impact/urgency signals.
3) Extract artifacts: repro steps; expected vs actual; error messages/logs; links/IDs/filenames/code snippets (lightly cleaned, meaning preserved).
4) Produce one consolidated narrative + structured ticket.

Output (STRICT markdown template)
Title:
- Concise, specific, action-oriented.

Summary:
- 2–5 sentences: what’s wrong/needed, who is affected, why it matters.

Background / Context:
- Relevant history + constraints.

Current Behavior (Actual):
- Bullets: symptoms, observed outputs, error text.

Expected Behavior:
- Bullets: success definition.

Requirements:
- Bullets: explicit requirements + constraints (performance, compatibility, compliance, UX, scope limits).

Out of Scope:
- Bullets: exclusions stated/implied; if none, “Not provided”.

Reproduction Steps:
- Numbered steps; if unavailable, “Not provided”.

Environment:
- OS, app version, browser, device, deployment, flags, configs; use “Unknown” when missing.

Evidence:
- Verbatim logs/errors, screenshot/attachment references, links, file paths, IDs.

Decisions / Agreements:
- Bullets with attribution where needed.

Open Items / Unknowns:
- Bullets of missing info that blocks execution (no questions).

Risks / Dependencies:
- Bullets of dependencies/integrations/approvals/known risks.

Acceptance Criteria:
- Testable checklist derived from requirements + expected behavior.
- If vague, translate to minimal testable criteria without adding scope.

Priority & Severity (if inferable from text):
- Priority: P0–P3
- Severity: S0–S3
- If not clearly supported, “Not provided”.

Labels (optional):
- 3–8 tags only if supported by the conversation.

Style
- Crisp bullets; no filler; self-contained ticket understandable without the conversation.
```

```md
#2
[Directive]
Convert the provided user↔assistant conversation export into ONE consolidated issue ticket.

Rules
- Do NOT add facts. Do NOT guess. Do NOT ask questions.
- Keep all material details; remove chit-chat and duplicates.
- If info is missing, write “Unknown” or “Not provided”.
- If the conversation conflicts, list both versions with attribution (per user / per assistant); don’t resolve.
- Redact credentials/sensitive personal data as “[REDACTED]” and note the redaction.
- For critical/ambiguous points, add attribution and optionally a ≤25-word quote fragment.

Extract
- Primary + secondary issues (if any)
- Target system/component, stakeholders/owners (if stated)
- Requirements + constraints
- Attempted fixes, errors/logs (verbatim), links/IDs/files/code snippets
- Steps to reproduce (if present)
- Impact/urgency cues, risks/dependencies, decisions, next steps

Output only this markdown ticket (headings and order must match)
Title:
Summary:
Background / Context:
Current Behavior (Actual):
Expected Behavior:
Requirements:
Out of Scope:
Reproduction Steps:
Environment:
Evidence:
Decisions / Agreements:
Open Items / Unknowns:
Risks / Dependencies:
Acceptance Criteria:
Priority & Severity (if inferable from text):
Labels (optional):
```

```md
#3
[QA-Ready]
SYSTEM: Conversation-to-Issue-Ticket Converter

Input
- {conversation_export}

Hard validations
- Output ONLY a single issue ticket in markdown.
- Include EVERY section in the specified order, even if content is “Unknown/Not provided”.
- No questions. No speculative language. No new facts.
- Conflicts: explicitly surface contradictions with attribution (User vs Assistant).
- Evidence: keep error/log text verbatim; lightly clean code/paths without changing meaning.
- Privacy: redact any secrets or sensitive personal data as “[REDACTED]”.

Ticket template (must match exactly)
Title:
Summary:
Background / Context:
Current Behavior (Actual):
Expected Behavior:
Requirements:
Out of Scope:
Reproduction Steps:
Environment:
Evidence:
Decisions / Agreements:
Open Items / Unknowns:
Risks / Dependencies:
Acceptance Criteria:
Priority & Severity (if inferable from text):
Labels (optional):

Acceptance Criteria guidance
- Convert requirements into testable checks (Given/When/Then style acceptable).
- If requirements are vague, produce minimal testable criteria without expanding scope.

Style
- Bullets preferred; concise, concrete nouns/verbs; self-contained.
```


--- examples/user-input.md ---

```md primary.md
You are **Hiro — Prompt Optimization Specialist**. Transform any raw user prompt into up to **4 concise, high-leverage variants** that preserve intent while improving clarity, constraints, and outcome specificity.

**Your job**

* Keep the user’s original goal intact. Remove fluff, tighten verbs, and make deliverables and success criteria explicit.
* Resolve ambiguity with **neutral defaults** or **clearly marked placeholders** like `{context}`, `{inputs}`, `{constraints}`, `{acceptance_criteria}`, `{format}`, `{deadline}`.
* Add structure (steps, bullets, numbered requirements) only when it improves execution.
* Match or gently improve the **tone** implied by the user (directive/spec-like, polite, collaborative). Never over-polish into marketing-speak.
* Do **not** introduce tools, external data, or scope changes unless the user asked for them.
* Prefer active voice, testable requirements, and measurable outputs.

**Output rules**

* Return **only** the variants, each in its **own fenced code block**. No commentary, no preamble, no trailing notes.
* Produce **1–4 variants** (default 3). Stop at 4 unless the user explicitly requests more. Number each (#1,#2,#n).
* For each block, begin with a short bracketed style tag (e.g., `[Directive]`, `[Spec]`, `[Polite]`, `[QA-Ready]`) on the first line, then the optimized prompt on subsequent lines.

**Optimization checklist (apply silently)**

* Clarify objective and end artifact.
* Specify audience/user/environment if implied.
* Pin input sources and constraints.
* Define acceptance criteria and non-goals.
* State format/structure and any length limits.
* Include edge cases or examples if the user hinted at them.
* Keep placeholders where the user must decide.

**Now optimize the next input.**
User prompt: {paste user’s raw prompt here}
```

---

```md secondary.md

SYSTEM: Conversation-to-Issue-Ticket Converter

Role
- Convert an exported user↔assistant conversation into a single cleaned, coherent issue ticket.
- Preserve all key details. Remove noise. Do not add new facts.

Input
- An exported conversation containing alternating messages from “user” and “assistant” (any format: plain text, markdown, JSON-like, timestamps optional).

Core rules
- Fidelity: Do not invent requirements, causes, decisions, timelines, metrics, or outcomes.
- Completeness: Keep every materially relevant detail (goals, constraints, edge cases, decisions, rejected options, action items, dependencies, risks).
- No questions: Do not ask the reader for missing info. If information is missing, mark it explicitly as “Unknown” or “Not provided”.
- De-duplication: Merge repeats and restatements. Keep the clearest formulation.
- Conflict handling: If the conversation contradicts itself, report both versions and attribute them (User vs Assistant). Do not resolve by guessing.
- Terminology: Normalize names and terms (features, components, people, systems) using the most consistent wording from the conversation.
- Traceability: When a detail is critical or ambiguous, include a short attributed quote fragment (≤25 words) or “(per user)” / “(per assistant)”.
- Security/privacy: Keep secrets out. If the conversation includes credentials or sensitive personal data, redact and note “[REDACTED]”.
- Output only the issue ticket. No meta-commentary.

Process
1) Parse and segment the conversation into: problem statement(s), context, requirements, constraints, environment, attempted fixes, errors, decisions, next steps.
2) Identify:
   - Primary issue
   - Secondary issues (if present)
   - Stakeholders/owners (if stated)
   - Target system/component
   - Impact and urgency signals
3) Extract concrete artifacts:
   - Steps to reproduce
   - Expected vs actual behavior
   - Error messages/logs
   - Links, IDs, filenames, code snippets (lightly cleaned; preserve meaning)
4) Produce one consolidated, coherent narrative and a structured ticket.

Output format (strict)
Title:
- Concise, specific, action-oriented. Keep short.

Summary:
- 2–5 sentences describing what’s wrong / needed, who is affected, and why it matters.

Background / Context:
- Relevant history and constraints from the conversation.

Current Behavior (Actual):
- Bullet list. Include symptoms, observed outputs, error text.

Expected Behavior:
- Bullet list. Clear success definition.

Requirements:
- Bullet list of explicit requirements extracted from the conversation.
- Include constraints (performance, compatibility, compliance, UX, scope limits).

Out of Scope:
- Bullet list of exclusions stated or implied by the conversation. If none, “Not provided”.

Reproduction Steps:
- Numbered steps. If not available, “Not provided”.

Environment:
- OS, app version, browser, device, deployment, flags, configs. Use “Unknown” when missing.

Evidence:
- Logs/errors (verbatim), screenshots/attachments references, links, file paths, IDs.

Decisions / Agreements:
- Bullet list of decisions made in the conversation, with attribution where needed.

Open Items / Unknowns:
- Bullet list of missing info that blocks execution. No questions; just state unknowns.

Risks / Dependencies:
- Bullet list of dependencies, integrations, approvals, or known risks mentioned.

Acceptance Criteria:
- Testable checklist statements. Derive from requirements and expected behavior.
- If requirements are vague, translate into minimal testable criteria without adding new scope.

Priority & Severity (if inferable from text):
- Priority: P0–P3
- Severity: S0–S3
- Only infer if conversation provides clear cues; otherwise “Not provided”.

Labels (optional):
- 3–8 tags (e.g., bug, enhancement, auth, ui, performance). Only if supported by conversation.

Style constraints
- Use crisp bullet points. No filler.
- Prefer concrete nouns/verbs over abstract phrasing.
- Keep ticket self-contained and understandable without reading the conversation.



```


--- .taskmaster/templates/example_prd.txt ---
<context>
# Overview  
[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.]

# Core Features  
[List and describe the main features of your product. For each feature, include:
- What it does
- Why it's important
- How it works at a high level]

# User Experience  
[Describe the user journey and experience. Include:
- User personas
- Key user flows
- UI/UX considerations]
</context>
<PRD>
# Technical Architecture  
[Outline the technical implementation details:
- System components
- Data models
- APIs and integrations
- Infrastructure requirements]

# Development Roadmap  
[Break down the development process into phases:
- MVP requirements
- Future enhancements
- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks]

# Logical Dependency Chain
[Define the logical order of development:
- Which features need to be built first (foundation)
- Getting as quickly as possible to something usable/visible front end that works
- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches]

# Risks and Mitigations  
[Identify potential risks and how they'll be addressed:
- Technical challenges
- Figuring out the MVP that we can build upon
- Resource constraints]

# Appendix  
[Include any additional information:
- Research findings
- Technical specifications]
</PRD>

--- .taskmaster/templates/example_prd_rpg.txt ---
<rpg-method>
# Repository Planning Graph (RPG) Method - PRD Template

This template teaches you (AI or human) how to create structured, dependency-aware PRDs using the RPG methodology from Microsoft Research. The key insight: separate WHAT (functional) from HOW (structural), then connect them with explicit dependencies.

## Core Principles

1. **Dual-Semantics**: Think functional (capabilities) AND structural (code organization) separately, then map them
2. **Explicit Dependencies**: Never assume - always state what depends on what
3. **Topological Order**: Build foundation first, then layers on top
4. **Progressive Refinement**: Start broad, refine iteratively

## How to Use This Template

- Follow the instructions in each `<instruction>` block
- Look at `<example>` blocks to see good vs bad patterns
- Fill in the content sections with your project details
- The AI reading this will learn the RPG method by following along
- Task Master will parse the resulting PRD into dependency-aware tasks

## Recommended Tools for Creating PRDs

When using this template to **create** a PRD (not parse it), use **code-context-aware AI assistants** for best results:

**Why?** The AI needs to understand your existing codebase to make good architectural decisions about modules, dependencies, and integration points.

**Recommended tools:**
- **Claude Code** (claude-code CLI) - Best for structured reasoning and large contexts
- **Cursor/Windsurf** - IDE integration with full codebase context
- **Gemini CLI** (gemini-cli) - Massive context window for large codebases
- **Codex/Grok CLI** - Strong code generation with context awareness

**Note:** Once your PRD is created, `task-master parse-prd` works with any configured AI model - it just needs to read the PRD text itself, not your codebase.
</rpg-method>

---

<overview>
<instruction>
Start with the problem, not the solution. Be specific about:
- What pain point exists?
- Who experiences it?
- Why existing solutions don't work?
- What success looks like (measurable outcomes)?

Keep this section focused - don't jump into implementation details yet.
</instruction>

## Problem Statement
[Describe the core problem. Be concrete about user pain points.]

## Target Users
[Define personas, their workflows, and what they're trying to achieve.]

## Success Metrics
[Quantifiable outcomes. Examples: "80% task completion via autopilot", "< 5% manual intervention rate"]

</overview>

---

<functional-decomposition>
<instruction>
Now think about CAPABILITIES (what the system DOES), not code structure yet.

Step 1: Identify high-level capability domains
- Think: "What major things does this system do?"
- Examples: Data Management, Core Processing, Presentation Layer

Step 2: For each capability, enumerate specific features
- Use explore-exploit strategy:
  * Exploit: What features are REQUIRED for core value?
  * Explore: What features make this domain COMPLETE?

Step 3: For each feature, define:
- Description: What it does in one sentence
- Inputs: What data/context it needs
- Outputs: What it produces/returns
- Behavior: Key logic or transformations

<example type="good">
Capability: Data Validation
  Feature: Schema validation
    - Description: Validate JSON payloads against defined schemas
    - Inputs: JSON object, schema definition
    - Outputs: Validation result (pass/fail) + error details
    - Behavior: Iterate fields, check types, enforce constraints

  Feature: Business rule validation
    - Description: Apply domain-specific validation rules
    - Inputs: Validated data object, rule set
    - Outputs: Boolean + list of violated rules
    - Behavior: Execute rules sequentially, short-circuit on failure
</example>

<example type="bad">
Capability: validation.js
  (Problem: This is a FILE, not a CAPABILITY. Mixing structure into functional thinking.)

Capability: Validation
  Feature: Make sure data is good
  (Problem: Too vague. No inputs/outputs. Not actionable.)
</example>
</instruction>

## Capability Tree

### Capability: [Name]
[Brief description of what this capability domain covers]

#### Feature: [Name]
- **Description**: [One sentence]
- **Inputs**: [What it needs]
- **Outputs**: [What it produces]
- **Behavior**: [Key logic]

#### Feature: [Name]
- **Description**:
- **Inputs**:
- **Outputs**:
- **Behavior**:

### Capability: [Name]
...

</functional-decomposition>

---

<structural-decomposition>
<instruction>
NOW think about code organization. Map capabilities to actual file/folder structure.

Rules:
1. Each capability maps to a module (folder or file)
2. Features within a capability map to functions/classes
3. Use clear module boundaries - each module has ONE responsibility
4. Define what each module exports (public interface)

The goal: Create a clear mapping between "what it does" (functional) and "where it lives" (structural).

<example type="good">
Capability: Data Validation
  → Maps to: src/validation/
    ├── schema-validator.js      (Schema validation feature)
    ├── rule-validator.js         (Business rule validation feature)
    └── index.js                  (Public exports)

Exports:
  - validateSchema(data, schema)
  - validateRules(data, rules)
</example>

<example type="bad">
Capability: Data Validation
  → Maps to: src/utils.js
  (Problem: "utils" is not a clear module boundary. Where do I find validation logic?)

Capability: Data Validation
  → Maps to: src/validation/everything.js
  (Problem: One giant file. Features should map to separate files for maintainability.)
</example>
</instruction>

## Repository Structure

```
project-root/
├── src/
│   ├── [module-name]/       # Maps to: [Capability Name]
│   │   ├── [file].js        # Maps to: [Feature Name]
│   │   └── index.js         # Public exports
│   └── [module-name]/
├── tests/
└── docs/
```

## Module Definitions

### Module: [Name]
- **Maps to capability**: [Capability from functional decomposition]
- **Responsibility**: [Single clear purpose]
- **File structure**:
  ```
  module-name/
  ├── feature1.js
  ├── feature2.js
  └── index.js
  ```
- **Exports**:
  - `functionName()` - [what it does]
  - `ClassName` - [what it does]

</structural-decomposition>

---

<dependency-graph>
<instruction>
This is THE CRITICAL SECTION for Task Master parsing.

Define explicit dependencies between modules. This creates the topological order for task execution.

Rules:
1. List modules in dependency order (foundation first)
2. For each module, state what it depends on
3. Foundation modules should have NO dependencies
4. Every non-foundation module should depend on at least one other module
5. Think: "What must EXIST before I can build this module?"

<example type="good">
Foundation Layer (no dependencies):
  - error-handling: No dependencies
  - config-manager: No dependencies
  - base-types: No dependencies

Data Layer:
  - schema-validator: Depends on [base-types, error-handling]
  - data-ingestion: Depends on [schema-validator, config-manager]

Core Layer:
  - algorithm-engine: Depends on [base-types, error-handling]
  - pipeline-orchestrator: Depends on [algorithm-engine, data-ingestion]
</example>

<example type="bad">
- validation: Depends on API
- API: Depends on validation
(Problem: Circular dependency. This will cause build/runtime issues.)

- user-auth: Depends on everything
(Problem: Too many dependencies. Should be more focused.)
</example>
</instruction>

## Dependency Chain

### Foundation Layer (Phase 0)
No dependencies - these are built first.

- **[Module Name]**: [What it provides]
- **[Module Name]**: [What it provides]

### [Layer Name] (Phase 1)
- **[Module Name]**: Depends on [[module-from-phase-0], [module-from-phase-0]]
- **[Module Name]**: Depends on [[module-from-phase-0]]

### [Layer Name] (Phase 2)
- **[Module Name]**: Depends on [[module-from-phase-1], [module-from-foundation]]

[Continue building up layers...]

</dependency-graph>

---

<implementation-roadmap>
<instruction>
Turn the dependency graph into concrete development phases.

Each phase should:
1. Have clear entry criteria (what must exist before starting)
2. Contain tasks that can be parallelized (no inter-dependencies within phase)
3. Have clear exit criteria (how do we know phase is complete?)
4. Build toward something USABLE (not just infrastructure)

Phase ordering follows topological sort of dependency graph.

<example type="good">
Phase 0: Foundation
  Entry: Clean repository
  Tasks:
    - Implement error handling utilities
    - Create base type definitions
    - Setup configuration system
  Exit: Other modules can import foundation without errors

Phase 1: Data Layer
  Entry: Phase 0 complete
  Tasks:
    - Implement schema validator (uses: base types, error handling)
    - Build data ingestion pipeline (uses: validator, config)
  Exit: End-to-end data flow from input to validated output
</example>

<example type="bad">
Phase 1: Build Everything
  Tasks:
    - API
    - Database
    - UI
    - Tests
  (Problem: No clear focus. Too broad. Dependencies not considered.)
</example>
</instruction>

## Development Phases

### Phase 0: [Foundation Name]
**Goal**: [What foundational capability this establishes]

**Entry Criteria**: [What must be true before starting]

**Tasks**:
- [ ] [Task name] (depends on: [none or list])
  - Acceptance criteria: [How we know it's done]
  - Test strategy: [What tests prove it works]

- [ ] [Task name] (depends on: [none or list])

**Exit Criteria**: [Observable outcome that proves phase complete]

**Delivers**: [What can users/developers do after this phase?]

---

### Phase 1: [Layer Name]
**Goal**:

**Entry Criteria**: Phase 0 complete

**Tasks**:
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])

**Exit Criteria**:

**Delivers**:

---

[Continue with more phases...]

</implementation-roadmap>

---

<test-strategy>
<instruction>
Define how testing will be integrated throughout development (TDD approach).

Specify:
1. Test pyramid ratios (unit vs integration vs e2e)
2. Coverage requirements
3. Critical test scenarios
4. Test generation guidelines for Surgical Test Generator

This section guides the AI when generating tests during the RED phase of TDD.

<example type="good">
Critical Test Scenarios for Data Validation module:
  - Happy path: Valid data passes all checks
  - Edge cases: Empty strings, null values, boundary numbers
  - Error cases: Invalid types, missing required fields
  - Integration: Validator works with ingestion pipeline
</example>
</instruction>

## Test Pyramid

```
        /\
       /E2E\       ← [X]% (End-to-end, slow, comprehensive)
      /------\
     /Integration\ ← [Y]% (Module interactions)
    /------------\
   /  Unit Tests  \ ← [Z]% (Fast, isolated, deterministic)
  /----------------\
```

## Coverage Requirements
- Line coverage: [X]% minimum
- Branch coverage: [X]% minimum
- Function coverage: [X]% minimum
- Statement coverage: [X]% minimum

## Critical Test Scenarios

### [Module/Feature Name]
**Happy path**:
- [Scenario description]
- Expected: [What should happen]

**Edge cases**:
- [Scenario description]
- Expected: [What should happen]

**Error cases**:
- [Scenario description]
- Expected: [How system handles failure]

**Integration points**:
- [What interactions to test]
- Expected: [End-to-end behavior]

## Test Generation Guidelines
[Specific instructions for Surgical Test Generator about what to focus on, what patterns to follow, project-specific test conventions]

</test-strategy>

---

<architecture>
<instruction>
Describe technical architecture, data models, and key design decisions.

Keep this section AFTER functional/structural decomposition - implementation details come after understanding structure.
</instruction>

## System Components
[Major architectural pieces and their responsibilities]

## Data Models
[Core data structures, schemas, database design]

## Technology Stack
[Languages, frameworks, key libraries]

**Decision: [Technology/Pattern]**
- **Rationale**: [Why chosen]
- **Trade-offs**: [What we're giving up]
- **Alternatives considered**: [What else we looked at]

</architecture>

---

<risks>
<instruction>
Identify risks that could derail development and how to mitigate them.

Categories:
- Technical risks (complexity, unknowns)
- Dependency risks (blocking issues)
- Scope risks (creep, underestimation)
</instruction>

## Technical Risks
**Risk**: [Description]
- **Impact**: [High/Medium/Low - effect on project]
- **Likelihood**: [High/Medium/Low]
- **Mitigation**: [How to address]
- **Fallback**: [Plan B if mitigation fails]

## Dependency Risks
[External dependencies, blocking issues]

## Scope Risks
[Scope creep, underestimation, unclear requirements]

</risks>

---

<appendix>
## References
[Papers, documentation, similar systems]

## Glossary
[Domain-specific terms]

## Open Questions
[Things to resolve during development]
</appendix>

---

<task-master-integration>
# How Task Master Uses This PRD

When you run `task-master parse-prd <file>.txt`, the parser:

1. **Extracts capabilities** → Main tasks
   - Each `### Capability:` becomes a top-level task

2. **Extracts features** → Subtasks
   - Each `#### Feature:` becomes a subtask under its capability

3. **Parses dependencies** → Task dependencies
   - `Depends on: [X, Y]` sets task.dependencies = ["X", "Y"]

4. **Orders by phases** → Task priorities
   - Phase 0 tasks = highest priority
   - Phase N tasks = lower priority, properly sequenced

5. **Uses test strategy** → Test generation context
   - Feeds test scenarios to Surgical Test Generator during implementation

**Result**: A dependency-aware task graph that can be executed in topological order.

## Why RPG Structure Matters

Traditional flat PRDs lead to:
- ❌ Unclear task dependencies
- ❌ Arbitrary task ordering
- ❌ Circular dependencies discovered late
- ❌ Poorly scoped tasks

RPG-structured PRDs provide:
- ✅ Explicit dependency chains
- ✅ Topological execution order
- ✅ Clear module boundaries
- ✅ Validated task graph before implementation

## Tips for Best Results

1. **Spend time on dependency graph** - This is the most valuable section for Task Master
2. **Keep features atomic** - Each feature should be independently testable
3. **Progressive refinement** - Start broad, use `task-master expand` to break down complex tasks
4. **Use research mode** - `task-master parse-prd --research` leverages AI for better task generation
</task-master-integration>


--- benchmarks/demo/71d38850b64a/composed_system.txt ---
You are a world-class creative writer.
Your goal is to fulfill the user request with elegance and precision.

{{USER_PROMPT}}

User prompt: Write a haiku about a rusty robot in a garden.



--- benchmarks/demo/71d38850b64a/output.txt ---
--- Assistant Response ---
I have analyzed your request and generated this response based on the provided template.
Input received via stdin:
You are a world-class creative writer.
Your goal is to fulfill the user request with elegance and precision.

{{USER_PROMPT}}

User prompt: Write a haiku about a rusty robot in a garden.



--- .tickets/mcp/promptbench_mcp_server_design_implementation.md ---
# promptbench-mcp-server

## tree

```text
promptbench-mcp-server/
  pyproject.toml
  README.md
  promptbench_mcp/
    __init__.py
    server.py
    models.py
    prompt_templates.py
    promptbench_cli.py
    security.py
  tests/
    test_security.py
```

---

## promptbench-mcp-server/pyproject.toml

```toml
# path: promptbench-mcp-server/pyproject.toml
[project]
name = "promptbench-mcp-server"
version = "0.1.0"
description = "MCP server exposing promptbench as tools (matrix, prompt generation, run)"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
  "mcp>=1.8.0",
  "pydantic>=2.6.0",
]

[project.scripts]
promptbench-mcp = "promptbench_mcp.server:main"

[tool.pytest.ini_options]
addopts = "-q"
```

---

## promptbench-mcp-server/README.md

```md
<!-- path: promptbench-mcp-server/README.md -->
# promptbench-mcp-server

Expose `promptbench` as an MCP server so agents/assistants can:
- read workspace health (doctor)
- discover inputs
- build a matrix (templates/payloads/skills/providers)
- generate a prompt-engineer payload (from structured fields)
- run selected jobs via promptbench

## Requirements
- Python 3.11+
- `promptbench` installed/available in the same environment (the server shells out to `python3 -m promptbench ...`).

## Install (uv)
```bash
# shorthand: uv venv && uv pip install -e .
uv venv
uv pip install -e .

# ensure promptbench is available too (either installed or editable from your repo)
# uv pip install -e ../promptbench
```

## Run (stdio)
```bash
promptbench-mcp
```

## Run (streamable HTTP)
```bash
promptbench-mcp --transport streamable_http --host 127.0.0.1 --port 8765
```

## Security model
The server restricts file IO to allowlisted roots.

Configure allowlisted roots (colon-separated):
```bash
export PROMPTBENCH_MCP_ROOTS="/path/to/your/workspaces:/another/root"
```

Default: current working directory.

## Typical agent flow
1) `promptbench_matrix(config_path)` → choose templates/payloads/skills/providers
2) `promptbench_render_prompt_engineer_payload(...)` → get markdown
3) `promptbench_write_payload(...)` → save payload into `.promptbench/payloads/_mcp/`
4) `promptbench_run(...)` → run selected jobs + return summary
```

---

## promptbench-mcp-server/promptbench_mcp/__init__.py

```python
# path: promptbench-mcp-server/promptbench_mcp/__init__.py
__all__ = ["__version__"]

__version__ = "0.1.0"
```

---

## promptbench-mcp-server/promptbench_mcp/security.py

```python
# path: promptbench-mcp-server/promptbench_mcp/security.py
from __future__ import annotations

import os
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable


DEFAULT_MAX_BYTES: int = 2_000_000  # 2MB safety limit for file reads


@dataclass(frozen=True)
class RootPolicy:
    """Defines which filesystem roots are allowed for read/write operations."""

    roots: tuple[Path, ...]

    @staticmethod
    def from_env(env_var: str = "PROMPTBENCH_MCP_ROOTS") -> "RootPolicy":
        raw = os.environ.get(env_var, "").strip()
        if not raw:
            # Default to current working directory.
            return RootPolicy(roots=(Path.cwd().resolve(),))
        parts = [p.strip() for p in raw.split(os.pathsep) if p.strip()]
        roots = tuple(Path(p).expanduser().resolve() for p in parts)
        return RootPolicy(roots=roots)


def _is_relative_to(candidate: Path, root: Path) -> bool:
    try:
        candidate.resolve().is_relative_to(root)
        return True
    except Exception:
        return False


def resolve_under_roots(path: str | Path, policy: RootPolicy) -> Path:
    p = Path(path).expanduser().resolve()
    for r in policy.roots:
        if _is_relative_to(p, r):
            return p
    raise ValueError(
        f"Path not allowed: {p}. Set PROMPTBENCH_MCP_ROOTS to allow it."
    )


def ensure_dir(path: Path) -> None:
    path.mkdir(parents=True, exist_ok=True)


def safe_read_text(path: Path, *, max_bytes: int = DEFAULT_MAX_BYTES, encoding: str = "utf-8") -> str:
    st = path.stat()
    if st.st_size > max_bytes:
        raise ValueError(f"Refusing to read {st.st_size} bytes (limit={max_bytes}).")
    return path.read_text(encoding=encoding)


def safe_write_text(
    path: Path,
    content: str,
    *,
    overwrite: bool,
    encoding: str = "utf-8",
) -> None:
    if path.exists() and not overwrite:
        raise ValueError(
            f"Refusing to overwrite existing file: {path}. Set overwrite=true to replace it."
        )
    ensure_dir(path.parent)
    path.write_text(content, encoding=encoding)


def normalize_selection_list(items: Iterable[str] | None) -> list[str]:
    if not items:
        return []
    out: list[str] = []
    for x in items:
        s = (x or "").strip()
        if s:
            out.append(s)
    return out
```

---

## promptbench-mcp-server/promptbench_mcp/promptbench_cli.py

```python
# path: promptbench-mcp-server/promptbench_mcp/promptbench_cli.py
from __future__ import annotations

import asyncio
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable

from .security import RootPolicy, resolve_under_roots


@dataclass(frozen=True)
class SubprocessResult:
    stdout: str
    stderr: str
    exit_code: int


async def _run_python_module(
    module: str,
    args: list[str],
    *,
    timeout_s: float,
    cwd: Path | None,
    env: dict[str, str] | None,
    max_stdout_bytes: int = 5_000_000,
    max_stderr_bytes: int = 2_000_000,
) -> SubprocessResult:
    """Run `python3 -m <module> ...` safely (no shell)."""

    proc = await asyncio.create_subprocess_exec(
        "python3",
        "-m",
        module,
        *args,
        cwd=str(cwd) if cwd else None,
        env=env,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )

    try:
        stdout_b, stderr_b = await asyncio.wait_for(proc.communicate(), timeout=timeout_s)
    except asyncio.TimeoutError:
        proc.kill()
        await proc.communicate()
        raise TimeoutError(f"Subprocess timed out after {timeout_s}s")

    # Truncate aggressively to avoid context blowups.
    stdout_b = stdout_b[:max_stdout_bytes]
    stderr_b = stderr_b[:max_stderr_bytes]

    return SubprocessResult(
        stdout=stdout_b.decode("utf-8", errors="replace"),
        stderr=stderr_b.decode("utf-8", errors="replace"),
        exit_code=int(proc.returncode or 0),
    )


def _json_loads_or_error(text: str, *, label: str) -> Any:
    try:
        return json.loads(text)
    except json.JSONDecodeError as e:
        preview = text[:5000]
        raise ValueError(f"Failed to parse {label} as JSON: {e}. Output preview: {preview}")


def _csv(items: Iterable[str] | None) -> str | None:
    if not items:
        return None
    cleaned = [x.strip() for x in items if x and x.strip()]
    return ",".join(cleaned) if cleaned else None


async def promptbench_doctor(*, config_path: str | None, workspace_path: str | None, timeout_s: float) -> list[dict[str, Any]]:
    args: list[str] = ["doctor", "--json"]
    if config_path:
        args.extend(["--config", config_path])
    elif workspace_path:
        args.extend(["--path", workspace_path])
    else:
        raise ValueError("Either config_path or workspace_path is required")

    res = await _run_python_module("promptbench", args, timeout_s=timeout_s, cwd=None, env=None)
    if res.exit_code != 0 and not res.stdout.strip():
        raise RuntimeError(res.stderr.strip() or "promptbench doctor failed")

    return _json_loads_or_error(res.stdout, label="doctor output")


async def promptbench_discover(*, config_path: str, force_refresh: bool, timeout_s: float) -> dict[str, Any]:
    args = ["discover", "--config", config_path]
    if force_refresh:
        args.append("--force-refresh")

    res = await _run_python_module("promptbench", args, timeout_s=timeout_s, cwd=None, env=None)
    if res.exit_code != 0:
        raise RuntimeError(res.stderr.strip() or res.stdout.strip() or "promptbench discover failed")

    return _json_loads_or_error(res.stdout, label="discover output")


async def promptbench_matrix(*, config_path: str, force_refresh: bool, timeout_s: float) -> dict[str, Any]:
    args = ["matrix", "--config", config_path]
    if force_refresh:
        args.append("--force-refresh")

    res = await _run_python_module("promptbench", args, timeout_s=timeout_s, cwd=None, env=None)
    if res.exit_code != 0:
        raise RuntimeError(res.stderr.strip() or res.stdout.strip() or "promptbench matrix failed")

    return _json_loads_or_error(res.stdout, label="matrix output")


async def promptbench_run(
    *,
    config_path: str,
    output: str | None,
    limit: int | None,
    concurrency: int | None,
    providers: list[str] | None,
    templates: list[str] | None,
    payloads: list[str] | None,
    skills: list[str] | None,
    force_refresh: bool,
    timeout_s: float,
    event_log: str = "-",
) -> tuple[list[dict[str, Any]], SubprocessResult]:
    """Runs promptbench with JSONL events to stdout.

    Returns: (events, subprocess_result)
    """
    args: list[str] = ["--config", config_path, "--event-log", event_log]

    if output:
        args.extend(["--output", output])
    if limit is not None:
        args.extend(["--limit", str(limit)])
    if concurrency is not None:
        args.extend(["--concurrency", str(concurrency)])

    p_csv = _csv(providers)
    if p_csv:
        args.extend(["--providers", p_csv])

    t_csv = _csv(templates)
    if t_csv:
        args.extend(["--templates", t_csv])

    pl_csv = _csv(payloads)
    if pl_csv:
        args.extend(["--payloads", pl_csv])

    s_csv = _csv(skills)
    if s_csv:
        args.extend(["--skills", s_csv])

    if force_refresh:
        args.append("--force-refresh")

    res = await _run_python_module("promptbench", args, timeout_s=timeout_s, cwd=None, env=None)

    # stdout is expected to be JSONL events when event_log == '-'.
    events: list[dict[str, Any]] = []
    for line in res.stdout.splitlines():
        line = line.strip()
        if not line:
            continue
        try:
            events.append(json.loads(line))
        except json.JSONDecodeError:
            # If promptbench ever prints non-JSON to stdout, keep a sentinel record.
            events.append({"type": "non_json_stdout", "text": line[:500]})

    return events, res


def validate_config_path(config_path: str, policy: RootPolicy) -> Path:
    p = resolve_under_roots(config_path, policy)
    if not p.exists():
        raise ValueError(f"Config not found: {p}")
    return p


def validate_workspace_path(workspace_path: str, policy: RootPolicy) -> Path:
    p = resolve_under_roots(workspace_path, policy)
    if not p.exists():
        raise ValueError(f"Workspace path not found: {p}")
    return p


def validate_any_path(path: str, policy: RootPolicy) -> Path:
    return resolve_under_roots(path, policy)
```

---

## promptbench-mcp-server/promptbench_mcp/prompt_templates.py

```python
# path: promptbench-mcp-server/promptbench_mcp/prompt_templates.py
from __future__ import annotations

from dataclasses import dataclass


PROMPT_ENGINEER_PAYLOAD_TEMPLATE = """We plan to use {user_idea} for our `{project_in_question}` tool. {additional_information}

Not always provided, inference from context or ask additional questions seeking what information you need to answer effective.

Our pain point: {pain_point}

```md {reference_file_name}
{reference_file_content}
```

### What we need from the prompt-engineer
- optimized prompt that will get the {target_agent} to find us a solution for adding capabilities to our existing {project} tool giving it {capability}

Our question to the prompt-engineer: {optimize_prompt}
"""


@dataclass(frozen=True)
class PromptEngineerPayload:
    markdown: str


def render_prompt_engineer_payload(
    *,
    user_idea: str,
    project_in_question: str,
    additional_information: str | None,
    pain_point: str | None,
    reference_file_name: str,
    reference_file_content: str,
    target_agent: str,
    project: str,
    capability: str,
    optimize_prompt: str,
) -> PromptEngineerPayload:
    ai = (additional_information or "").strip()
    pp = (pain_point or "").strip()

    if not ai:
        ai = ""
    if not pp:
        pp = "(not provided)"

    md = PROMPT_ENGINEER_PAYLOAD_TEMPLATE.format(
        user_idea=user_idea.strip(),
        project_in_question=project_in_question.strip(),
        additional_information=ai.strip(),
        pain_point=pp.strip(),
        reference_file_name=reference_file_name.strip(),
        reference_file_content=reference_file_content.rstrip(),
        target_agent=target_agent.strip(),
        project=project.strip(),
        capability=capability.strip(),
        optimize_prompt=optimize_prompt.strip(),
    )

    # Minor normalization to avoid accidental trailing whitespace walls.
    md = "\n".join([line.rstrip() for line in md.splitlines()]).strip() + "\n"

    return PromptEngineerPayload(markdown=md)
```

---

## promptbench-mcp-server/promptbench_mcp/models.py

```python
# path: promptbench-mcp-server/promptbench_mcp/models.py
from __future__ import annotations

from pathlib import Path
from typing import Any, Literal

from pydantic import BaseModel, Field


class DoctorInput(BaseModel):
    config_path: str | None = Field(default=None, description="Path to promptbench TOML config")
    workspace_path: str | None = Field(default=None, description="Workspace base directory")
    timeout_s: float = Field(default=10.0, ge=0.5, le=120.0)


class MatrixInput(BaseModel):
    config_path: str = Field(description="Path to promptbench TOML config")
    force_refresh: bool = Field(default=False)
    timeout_s: float = Field(default=20.0, ge=0.5, le=300.0)


class DiscoverInput(BaseModel):
    config_path: str = Field(description="Path to promptbench TOML config")
    force_refresh: bool = Field(default=False)
    timeout_s: float = Field(default=20.0, ge=0.5, le=300.0)


class ReadFileInput(BaseModel):
    path: str = Field(description="Path to read (must be under allowlisted roots)")
    max_bytes: int = Field(default=200000, ge=1, le=2_000_000)


class RenderPromptEngineerPayloadInput(BaseModel):
    user_idea: str
    project_in_question: str
    additional_information: str | None = None
    pain_point: str | None = None

    reference_file_name: str = Field(default="reference.md")
    reference_file_content: str | None = Field(default=None, description="Raw markdown to embed")
    reference_file_path: str | None = Field(default=None, description="Read markdown from this file")

    target_agent: str
    project: str
    capability: str
    optimize_prompt: str


class WritePayloadInput(BaseModel):
    config_path: str = Field(description="Path to promptbench TOML config (used to locate workspace)")
    filename: str = Field(description="Filename to write (e.g. prompt-engineer.md)")
    content: str = Field(description="Markdown content to write")
    overwrite: bool = Field(default=False)


class RunInput(BaseModel):
    config_path: str = Field(description="Path to promptbench TOML config")

    output: str | None = Field(default=None, description="Override output root directory")
    limit: int | None = Field(default=None, ge=1)
    concurrency: int | None = Field(default=None, ge=1, le=256)

    providers: list[str] | None = None
    templates: list[str] | None = None
    payloads: list[str] | None = None
    skills: list[str] | None = None

    force_refresh: bool = Field(default=False)

    timeout_s: float = Field(default=3600.0, ge=1.0, le=24 * 3600.0)

    max_events: int = Field(default=2000, ge=0, le=10000)
    events_tail: int = Field(default=200, ge=0, le=2000)


class RunResult(BaseModel):
    exit_code: int
    stderr_tail: str
    events_tail: list[dict[str, Any]]
    events_count: int


class WritePayloadResult(BaseModel):
    path: str
    bytes_written: int
```

---

## promptbench-mcp-server/promptbench_mcp/server.py

```python
# path: promptbench-mcp-server/promptbench_mcp/server.py
from __future__ import annotations

import argparse
from pathlib import Path
from typing import Any

from mcp.server.fastmcp import FastMCP, Context

from .models import (
    DoctorInput,
    MatrixInput,
    DiscoverInput,
    ReadFileInput,
    RenderPromptEngineerPayloadInput,
    WritePayloadInput,
    WritePayloadResult,
    RunInput,
    RunResult,
)
from .prompt_templates import render_prompt_engineer_payload
from .promptbench_cli import (
    promptbench_doctor,
    promptbench_discover,
    promptbench_matrix,
    promptbench_run,
    validate_any_path,
    validate_config_path,
    validate_workspace_path,
)
from .security import RootPolicy, safe_read_text, safe_write_text


mcp = FastMCP("promptbench_mcp")


def _policy(ctx: Context | None = None) -> RootPolicy:
    # Context exists for future extension (e.g., per-user policy).
    return RootPolicy.from_env()


def _workspace_from_config(config_path: Path) -> Path:
    # promptbench stores sets under: <workspace>/.promptbench/{templates,payloads,skills}
    return config_path.resolve().parent


def _payload_dir(workspace_root: Path) -> Path:
    return workspace_root / ".promptbench" / "payloads" / "_mcp"


@mcp.tool(
    name="promptbench_doctor",
    annotations={
        "title": "Promptbench: Doctor (workspace validation)",
        "readOnlyHint": True,
        "destructiveHint": False,
        "idempotentHint": True,
        "openWorldHint": False,
    },
)
async def tool_promptbench_doctor(params: DoctorInput, ctx: Context) -> list[dict[str, Any]]:
    """Validate a promptbench workspace and return diagnostics.

    Provide either `config_path` (preferred) or `workspace_path`.
    """
    policy = _policy(ctx)

    config_path = None
    workspace_path = None

    if params.config_path:
        config_path = str(validate_config_path(params.config_path, policy))
    if params.workspace_path:
        workspace_path = str(validate_workspace_path(params.workspace_path, policy))

    return await promptbench_doctor(
        config_path=config_path,
        workspace_path=workspace_path,
        timeout_s=params.timeout_s,
    )


@mcp.tool(
    name="promptbench_discover",
    annotations={
        "title": "Promptbench: Discover (preflight)",
        "readOnlyHint": True,
        "destructiveHint": False,
        "idempotentHint": True,
        "openWorldHint": False,
    },
)
async def tool_promptbench_discover(params: DiscoverInput, ctx: Context) -> dict[str, Any]:
    """Run `promptbench discover` and return the JSON preflight payload."""
    policy = _policy(ctx)
    _ = validate_config_path(params.config_path, policy)
    return await promptbench_discover(
        config_path=params.config_path,
        force_refresh=params.force_refresh,
        timeout_s=params.timeout_s,
    )


@mcp.tool(
    name="promptbench_matrix",
    annotations={
        "title": "Promptbench: Matrix (inputs + sets)",
        "readOnlyHint": True,
        "destructiveHint": False,
        "idempotentHint": True,
        "openWorldHint": False,
    },
)
async def tool_promptbench_matrix(params: MatrixInput, ctx: Context) -> dict[str, Any]:
    """Return the promptbench matrix JSON (templates/payloads/skills/providers + set files).

    This is equivalent to running: `python3 -m promptbench matrix --config <path>`.
    """
    policy = _policy(ctx)
    _ = validate_config_path(params.config_path, policy)
    return await promptbench_matrix(
        config_path=params.config_path,
        force_refresh=params.force_refresh,
        timeout_s=params.timeout_s,
    )


@mcp.tool(
    name="promptbench_read_file",
    annotations={
        "title": "Promptbench: Read file (allowlisted roots)",
        "readOnlyHint": True,
        "destructiveHint": False,
        "idempotentHint": True,
        "openWorldHint": False,
    },
)
async def tool_promptbench_read_file(params: ReadFileInput, ctx: Context) -> str:
    """Read a local file (restricted to allowlisted roots) and return its text."""
    policy = _policy(ctx)
    p = validate_any_path(params.path, policy)
    return safe_read_text(p, max_bytes=params.max_bytes)


@mcp.tool(
    name="promptbench_render_prompt_engineer_payload",
    annotations={
        "title": "Promptbench: Render prompt-engineer payload (markdown)",
        "readOnlyHint": True,
        "destructiveHint": False,
        "idempotentHint": True,
        "openWorldHint": False,
    },
)
async def tool_render_prompt_engineer_payload(params: RenderPromptEngineerPayloadInput, ctx: Context) -> str:
    """Render your prompt-engineer payload template to markdown.

    If `reference_file_path` is provided, the server will read it (must be allowlisted).
    Otherwise, provide `reference_file_content`.
    """
    policy = _policy(ctx)

    reference_content = params.reference_file_content
    if params.reference_file_path:
        p = validate_any_path(params.reference_file_path, policy)
        reference_content = safe_read_text(p)

    if reference_content is None:
        reference_content = ""

    rendered = render_prompt_engineer_payload(
        user_idea=params.user_idea,
        project_in_question=params.project_in_question,
        additional_information=params.additional_information,
        pain_point=params.pain_point,
        reference_file_name=params.reference_file_name,
        reference_file_content=reference_content,
        target_agent=params.target_agent,
        project=params.project,
        capability=params.capability,
        optimize_prompt=params.optimize_prompt,
    )
    return rendered.markdown


@mcp.tool(
    name="promptbench_write_payload",
    annotations={
        "title": "Promptbench: Write payload file into workspace",
        "readOnlyHint": False,
        "destructiveHint": True,
        "idempotentHint": False,
        "openWorldHint": False,
    },
)
async def tool_promptbench_write_payload(params: WritePayloadInput, ctx: Context) -> WritePayloadResult:
    """Write a payload markdown file under `<workspace>/.promptbench/payloads/_mcp/`.

    This is intentionally scoped so agents can create new payloads without arbitrary filesystem access.
    """
    policy = _policy(ctx)
    config_p = validate_config_path(params.config_path, policy)
    workspace_root = _workspace_from_config(config_p)

    # Ensure computed target stays under allowlisted roots.
    _ = validate_any_path(str(workspace_root), policy)

    target_dir = _payload_dir(workspace_root)
    target_path = (target_dir / params.filename).resolve()

    # Enforce that writes stay in the target dir.
    if target_dir.resolve() not in target_path.parents:
        raise ValueError("Invalid filename (path traversal detected)")

    safe_write_text(target_path, params.content, overwrite=params.overwrite)
    return WritePayloadResult(path=str(target_path), bytes_written=len(params.content.encode("utf-8")))


@mcp.tool(
    name="promptbench_run",
    annotations={
        "title": "Promptbench: Run selected jobs",
        "readOnlyHint": False,
        "destructiveHint": False,
        "idempotentHint": False,
        "openWorldHint": True,
    },
)
async def tool_promptbench_run(params: RunInput, ctx: Context) -> RunResult:
    """Run promptbench for the selected templates/payloads/skills/providers.

    Uses `--event-log -` so stdout is JSONL events; stderr is logs.

    Notes:
    - This tool may call external model providers.
    - It writes run artifacts into the configured output root.
    """
    policy = _policy(ctx)
    _ = validate_config_path(params.config_path, policy)

    events, proc = await promptbench_run(
        config_path=params.config_path,
        output=params.output,
        limit=params.limit,
        concurrency=params.concurrency,
        providers=params.providers,
        templates=params.templates,
        payloads=params.payloads,
        skills=params.skills,
        force_refresh=params.force_refresh,
        timeout_s=params.timeout_s,
        event_log="-",
    )

    # Avoid returning huge event streams by default.
    events_count = len(events)
    tail_n = min(params.events_tail, events_count)
    events_tail = events[-tail_n:] if tail_n > 0 else []

    # Keep a short stderr tail for debugging.
    stderr_tail = (proc.stderr or "")[-4000:]

    return RunResult(
        exit_code=proc.exit_code,
        stderr_tail=stderr_tail,
        events_tail=events_tail,
        events_count=events_count,
    )


def main() -> None:
    parser = argparse.ArgumentParser(description="promptbench MCP server")
    parser.add_argument(
        "--transport",
        default="stdio",
        choices=["stdio", "streamable_http"],
        help="Transport to use",
    )
    parser.add_argument("--host", default="127.0.0.1", help="Bind host (streamable_http)")
    parser.add_argument("--port", type=int, default=8765, help="Bind port (streamable_http)")

    args = parser.parse_args()

    if args.transport == "stdio":
        mcp.run()
    else:
        mcp.run(transport="streamable_http", host=args.host, port=args.port)


if __name__ == "__main__":
    main()
```

---

## promptbench-mcp-server/tests/test_security.py

```python
# path: promptbench-mcp-server/tests/test_security.py
from __future__ import annotations

import os
from pathlib import Path

import pytest

from promptbench_mcp.security import RootPolicy, resolve_under_roots


def test_resolve_under_roots_allows_within_root(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    root = tmp_path / "root"
    root.mkdir()
    child = root / "child" / "file.txt"
    child.parent.mkdir()
    child.write_text("x")

    monkeypatch.setenv("PROMPTBENCH_MCP_ROOTS", str(root))
    policy = RootPolicy.from_env()

    resolved = resolve_under_roots(child, policy)
    assert resolved == child.resolve()


def test_resolve_under_roots_denies_outside_root(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    root = tmp_path / "root"
    root.mkdir()

    outside = tmp_path / "outside.txt"
    outside.write_text("x")

    monkeypatch.setenv("PROMPTBENCH_MCP_ROOTS", str(root))
    policy = RootPolicy.from_env()

    with pytest.raises(ValueError):
        _ = resolve_under_roots(outside, policy)
```



--- .taskmaster/AGENTS.md ---
# Task Master AI - Agent Integration Guide

## Essential Commands

### Core Workflow Commands

```bash
# Project Setup
task-master init                                    # Initialize Task Master in current project
task-master parse-prd .taskmaster/docs/prd.md       # Generate tasks from PRD document
task-master models --setup                        # Configure AI models interactively

# Daily Development Workflow
task-master list                                   # Show all tasks with status
task-master next                                   # Get next available task to work on
task-master show <id>                             # View detailed task information (e.g., task-master show 1.2)
task-master set-status --id=<id> --status=done    # Mark task complete

# Task Management
task-master add-task --prompt="description" --research        # Add new task with AI assistance
task-master expand --id=<id> --research --force              # Break task into subtasks
task-master update-task --id=<id> --prompt="changes"         # Update specific task
task-master update --from=<id> --prompt="changes"            # Update multiple tasks from ID onwards
task-master update-subtask --id=<id> --prompt="notes"        # Add implementation notes to subtask

# Analysis & Planning
task-master analyze-complexity --research          # Analyze task complexity
task-master complexity-report                      # View complexity analysis
task-master expand --all --research               # Expand all eligible tasks

# Dependencies & Organization
task-master add-dependency --id=<id> --depends-on=<id>       # Add task dependency
task-master move --from=<id> --to=<id>                       # Reorganize task hierarchy
task-master validate-dependencies                            # Check for dependency issues
task-master generate                                         # Update task markdown files (usually auto-called)
```

## Key Files & Project Structure

### Core Files

- `.taskmaster/tasks/tasks.json` - Main task data file (auto-managed)
- `.taskmaster/config.json` - AI model configuration (use `task-master models` to modify)
- `.taskmaster/docs/prd.md` - Product Requirements Document for parsing (`.md` extension recommended for better editor support)
- `.taskmaster/tasks/*.txt` - Individual task files (auto-generated from tasks.json)
- `.env` - API keys for CLI usage

**PRD File Format:** While both `.txt` and `.md` extensions work, **`.md` is recommended** because:
- Markdown syntax highlighting in editors improves readability
- Proper rendering when previewing in VS Code, GitHub, or other tools
- Better collaboration through formatted documentation

### Claude Code Integration Files

- `CLAUDE.md` - Auto-loaded context for Claude Code (this file)
- `.claude/settings.json` - Claude Code tool allowlist and preferences
- `.claude/commands/` - Custom slash commands for repeated workflows
- `.mcp.json` - MCP server configuration (project-specific)

### Directory Structure

```
project/
├── .taskmaster/
│   ├── tasks/              # Task files directory
│   │   ├── tasks.json      # Main task database
│   │   ├── task-1.md      # Individual task files
│   │   └── task-2.md
│   ├── docs/              # Documentation directory
│   │   ├── prd.md         # Product requirements (.md recommended)
│   ├── reports/           # Analysis reports directory
│   │   └── task-complexity-report.json
│   ├── templates/         # Template files
│   │   └── example_prd.md  # Example PRD template (.md recommended)
│   └── config.json        # AI models & settings
├── .claude/
│   ├── settings.json      # Claude Code configuration
│   └── commands/         # Custom slash commands
├── .env                  # API keys
├── .mcp.json            # MCP configuration
└── CLAUDE.md            # This file - auto-loaded by Claude Code
```

## MCP Integration

Task Master provides an MCP server that Claude Code can connect to. Configure in `.mcp.json`:

```json
{
  "mcpServers": {
    "task-master-ai": {
      "command": "npx",
      "args": ["-y", "task-master-ai"],
      "env": {
        "TASK_MASTER_TOOLS": "core",
        "ANTHROPIC_API_KEY": "your_key_here",
        "PERPLEXITY_API_KEY": "your_key_here",
        "OPENAI_API_KEY": "OPENAI_API_KEY_HERE",
        "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE",
        "XAI_API_KEY": "XAI_API_KEY_HERE",
        "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE",
        "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE",
        "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE",
        "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE"
      }
    }
  }
}
```

### MCP Tool Tiers

Default: `core` (7 tools). Set via `TASK_MASTER_TOOLS` env var.

| Tier | Count | Tools |
|------|-------|-------|
| `core` | 7 | `get_tasks`, `next_task`, `get_task`, `set_task_status`, `update_subtask`, `parse_prd`, `expand_task` |
| `standard` | 14 | core + `initialize_project`, `analyze_project_complexity`, `expand_all`, `add_subtask`, `remove_task`, `add_task`, `complexity_report` |
| `all` | 44+ | standard + dependencies, tags, research, autopilot, scoping, models, rules |

**Upgrade when tool unavailable:** Edit MCP config, change `TASK_MASTER_TOOLS` from `"core"` to `"standard"` or `"all"`, restart MCP.

### Essential MCP Tools

```javascript
help; // = shows available taskmaster commands
// Project setup
initialize_project; // = task-master init
parse_prd; // = task-master parse-prd

// Daily workflow
get_tasks; // = task-master list
next_task; // = task-master next
get_task; // = task-master show <id>
set_task_status; // = task-master set-status

// Task management
add_task; // = task-master add-task
expand_task; // = task-master expand
update_task; // = task-master update-task
update_subtask; // = task-master update-subtask
update; // = task-master update

// Analysis
analyze_project_complexity; // = task-master analyze-complexity
complexity_report; // = task-master complexity-report
```

## Claude Code Workflow Integration

### Standard Development Workflow

#### 1. Project Initialization

```bash
# Initialize Task Master
task-master init

# Create or obtain PRD, then parse it (use .md extension for better editor support)
task-master parse-prd .taskmaster/docs/prd.md

# Analyze complexity and expand tasks
task-master analyze-complexity --research
task-master expand --all --research
```

If tasks already exist, another PRD can be parsed (with new information only!) using parse-prd with --append flag. This will add the generated tasks to the existing list of tasks..

#### 2. Daily Development Loop

```bash
# Start each session
task-master next                           # Find next available task
task-master show <id>                     # Review task details

# During implementation, check in code context into the tasks and subtasks
task-master update-subtask --id=<id> --prompt="implementation notes..."

# Complete tasks
task-master set-status --id=<id> --status=done
```

#### 3. Multi-Claude Workflows

For complex projects, use multiple Claude Code sessions:

```bash
# Terminal 1: Main implementation
cd project && claude

# Terminal 2: Testing and validation
cd project-test-worktree && claude

# Terminal 3: Documentation updates
cd project-docs-worktree && claude
```

### Custom Slash Commands

Create `.claude/commands/taskmaster-next.md`:

```markdown
Find the next available Task Master task and show its details.

Steps:

1. Run `task-master next` to get the next task
2. If a task is available, run `task-master show <id>` for full details
3. Provide a summary of what needs to be implemented
4. Suggest the first implementation step
```

Create `.claude/commands/taskmaster-complete.md`:

```markdown
Complete a Task Master task: $ARGUMENTS

Steps:

1. Review the current task with `task-master show $ARGUMENTS`
2. Verify all implementation is complete
3. Run any tests related to this task
4. Mark as complete: `task-master set-status --id=$ARGUMENTS --status=done`
5. Show the next available task with `task-master next`
```

## Tool Allowlist Recommendations

Add to `.claude/settings.json`:

```json
{
  "allowedTools": [
    "Edit",
    "Bash(task-master *)",
    "Bash(git commit:*)",
    "Bash(git add:*)",
    "Bash(npm run *)",
    "mcp__task_master_ai__*"
  ]
}
```

## Configuration & Setup

### API Keys Required

At least **one** of these API keys must be configured:

- `ANTHROPIC_API_KEY` (Claude models) - **Recommended**
- `PERPLEXITY_API_KEY` (Research features) - **Highly recommended**
- `OPENAI_API_KEY` (GPT models)
- `GOOGLE_API_KEY` (Gemini models)
- `MISTRAL_API_KEY` (Mistral models)
- `OPENROUTER_API_KEY` (Multiple models)
- `XAI_API_KEY` (Grok models)

An API key is required for any provider used across any of the 3 roles defined in the `models` command.

### Model Configuration

```bash
# Interactive setup (recommended)
task-master models --setup

# Set specific models
task-master models --set-main claude-3-5-sonnet-20241022
task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online
task-master models --set-fallback gpt-4o-mini
```

## Task Structure & IDs

### Task ID Format

- Main tasks: `1`, `2`, `3`, etc.
- Subtasks: `1.1`, `1.2`, `2.1`, etc.
- Sub-subtasks: `1.1.1`, `1.1.2`, etc.

### Task Status Values

- `pending` - Ready to work on
- `in-progress` - Currently being worked on
- `done` - Completed and verified
- `deferred` - Postponed
- `cancelled` - No longer needed
- `blocked` - Waiting on external factors

### Task Fields

```json
{
  "id": "1.2",
  "title": "Implement user authentication",
  "description": "Set up JWT-based auth system",
  "status": "pending",
  "priority": "high",
  "dependencies": ["1.1"],
  "details": "Use bcrypt for hashing, JWT for tokens...",
  "testStrategy": "Unit tests for auth functions, integration tests for login flow",
  "subtasks": []
}
```

## Claude Code Best Practices with Task Master

### Context Management

- Use `/clear` between different tasks to maintain focus
- This CLAUDE.md file is automatically loaded for context
- Use `task-master show <id>` to pull specific task context when needed

### Iterative Implementation

1. `task-master show <subtask-id>` - Understand requirements
2. Explore codebase and plan implementation
3. `task-master update-subtask --id=<id> --prompt="detailed plan"` - Log plan
4. `task-master set-status --id=<id> --status=in-progress` - Start work
5. Implement code following logged plan
6. `task-master update-subtask --id=<id> --prompt="what worked/didn't work"` - Log progress
7. `task-master set-status --id=<id> --status=done` - Complete task

### Complex Workflows with Checklists

For large migrations or multi-step processes:

1. Create a markdown PRD file describing the new changes: `touch task-migration-checklist.md` (prds can be .txt or .md)
2. Use Taskmaster to parse the new prd with `task-master parse-prd --append` (also available in MCP)
3. Use Taskmaster to expand the newly generated tasks into subtasks. Consdier using `analyze-complexity` with the correct --to and --from IDs (the new ids) to identify the ideal subtask amounts for each task. Then expand them.
4. Work through items systematically, checking them off as completed
5. Use `task-master update-subtask` to log progress on each task/subtask and/or updating/researching them before/during implementation if getting stuck

### Git Integration

Task Master works well with `gh` CLI:

```bash
# Create PR for completed task
gh pr create --title "Complete task 1.2: User authentication" --body "Implements JWT auth system as specified in task 1.2"

# Reference task in commits
git commit -m "feat: implement JWT auth (task 1.2)"
```

### Parallel Development with Git Worktrees

```bash
# Create worktrees for parallel task development
git worktree add ../project-auth feature/auth-system
git worktree add ../project-api feature/api-refactor

# Run Claude Code in each worktree
cd ../project-auth && claude    # Terminal 1: Auth work
cd ../project-api && claude     # Terminal 2: API work
```

## Troubleshooting

### AI Commands Failing

```bash
# Check API keys are configured
cat .env                           # For CLI usage

# Verify model configuration
task-master models

# Test with different model
task-master models --set-fallback gpt-4o-mini
```

### MCP Connection Issues

- Check `.mcp.json` configuration
- Verify Node.js installation
- Use `--mcp-debug` flag when starting Claude Code
- Use CLI as fallback if MCP unavailable

### Task File Sync Issues

```bash
# Regenerate task files from tasks.json
task-master generate

# Fix dependency issues
task-master fix-dependencies
```

DO NOT RE-INITIALIZE. That will not do anything beyond re-adding the same Taskmaster core files.

## Important Notes

### AI-Powered Operations

These commands make AI calls and may take up to a minute:

- `parse_prd` / `task-master parse-prd`
- `analyze_project_complexity` / `task-master analyze-complexity`
- `expand_task` / `task-master expand`
- `expand_all` / `task-master expand --all`
- `add_task` / `task-master add-task`
- `update` / `task-master update`
- `update_task` / `task-master update-task`
- `update_subtask` / `task-master update-subtask`

### File Management

- Never manually edit `tasks.json` - use commands instead
- Never manually edit `.taskmaster/config.json` - use `task-master models`
- Task markdown files in `tasks/` are auto-generated
- Run `task-master generate` after manual changes to tasks.json

### Claude Code Session Management

- Use `/clear` frequently to maintain focused context
- Create custom slash commands for repeated Task Master workflows
- Configure tool allowlist to streamline permissions
- Use headless mode for automation: `claude -p "task-master next"`

### Multi-Task Updates

- Use `update --from=<id>` to update multiple future tasks
- Use `update-task --id=<id>` for single task updates
- Use `update-subtask --id=<id>` for implementation logging

### Research Mode

- Add `--research` flag for research-based AI enhancement
- Requires a research model API key like Perplexity (`PERPLEXITY_API_KEY`) in environment
- Provides more informed task creation and updates
- Recommended for complex technical tasks

---

_This guide ensures Claude Code has immediate access to Task Master's essential functionality for agentic development workflows._


--- README.md ---
# prompt-bench

A systematic prompt benchmark runner for comparing system templates, user payloads, and optional "skills" across multiple local and CLI model providers.

## Overview

`prompt-bench` provides a repeatable way to run a Cartesian product of **System Templates × User Payloads × Optional Skills × Providers**. It acts as a "Fused Prompt Generator," taking a skeleton house, an influence prompt, and a target prompt to forge a single, unique, optimized output.

## Key Features

- **Triple-Layer Recursive Fusion**:
  - **The House (Skeleton)**: The outer meta-instruction (Template).
  - **The Influence (Primary)**: The style/optimization driver (Skill).
  - **The Target (Secondary)**: The core content to be transformed (Payload).
  - In `engine` mode, these are fused recursively by replacing instruction lines verbatim.
- **Multi-Mode Composition Engine**:
  - **Engine**: Strict spec-compliant recursive last-line replacement.
  - **Marker**: Standard `{{USER_PROMPT}}` variable replacement.
  - **Append**: Simple end-of-file concatenation.
- **Multi-Provider Support**:
  - **LM Studio**: OpenAI-compatible HTTP adapter with system-only fallback and auto-model unloading.
  - **Codex CLI**: Standardized execution of the `codex` tool.
  - **Gemini CLI**: Headless execution with optional JSON output parsing.
- **Atomic Artifact Capture**: Writes a dedicated directory per job containing the composed prompt, raw response, and a structured `output.json`.
- **Inputs Manifest**: Each run includes a lightweight `inputs.json` with template/payload/skill metadata.
- **Markdown Output Option**: Save assistant output as `output.md` when preferred.
- **Event Stream (JSONL)**: Optional lifecycle events for jobs and artifacts, used by the TUI.

## Installation & Setup

Ensure you have [uv](https://github.com/astral-sh/uv) installed. If you plan to build the TUI, install Go 1.24+.

### Automated Setup (Recommended)

```bash
git clone <repo-url>
cd prompt-bench
./scripts/setup.sh
```

This will install dependencies, create necessary directories, and generate sample input files.

### Run a Demo

```bash
./scripts/run_demo.sh
```

Demonstrates the artifact generation and recursive fusion logic using a local mock provider.

---

## Usage

### 1. Configure `config.toml` ([config example](demo_config.toml))

```toml
[inputs]
# Outer Skeleton
templates = "templates/prompt-composition-engine.md"
# Influence/Primary Snippets
skills = "skills/*.md"
# Target/Secondary Content
payloads = "payloads/*.md"

[runner]
composition_mode = "engine"
concurrency = 4
# Optional: use eventing executor without --event-log (JSONL emitted to stdout)
use_eventing_executor = true
# Optional: write assistant output to output.md instead of output.txt
write_markdown_output = true

[skill_router]
# Auto-map skills based on template/payload text.
enabled = true
# Override where skills are discovered (default: ~/.codex/skills)
skills_path = "~/.codex/skills"
# Force specific skills to always attach
force_skills = ["python"]
```

### 2. Run the Benchmark

```bash
uv run promptbench --config config.toml
```

### 3. Optional: emit an event stream

```bash
uv run promptbench --config config.toml --event-log -
```

You can also enable the eventing executor via config with `runner.use_eventing_executor = true`.

### 4. Optional: preflight discovery

```bash
uv run promptbench discover --config config.toml
```

---

## Composition Modes

### `engine` (Recommended for Prompt Generation)

Strictly follows the `templates/prompt-composition-engine.md` spec with a recursive twist:

1. **Inner Fusion**: The **Skill** (Influence) last line is replaced with `User prompt: [Payload Content]`.
2. **Outer Fusion**: The **Template** (House) last line is replaced with `User prompt: [Fused Skill Content]`.
3. **Preservation**: All trailing blank lines and verbatim characters are preserved.

### `marker`

Replaces the string `{{USER_PROMPT}}` in the template with the skill-injected payload.

### `append`

Adds a newline, the string `User prompt:`, and then the payload to the very end of the template.

---

## Artifact Structure

For every job, a directory is created in the output root:

```md
runs/
├── YYYY-MM-DD_HH-MM-SS-ffffff_<job_id>/
│   ├── composed_system.txt   # The final recursive prompt sent to the model
│   ├── output.txt            # The verbatim assistant response text (or output.md)
│   ├── output.json           # The raw JSON response (if available)
│   ├── inputs.json           # Template/payload/skill inputs for quick inspection
│   └── run.json              # Metadata, duration, and job-specific logs
└── summary.json              # Batch summary of all executed jobs
```

## TUI

The TUI lives under `tui/` as its own Go module.

Build and install (Linux/WSL + Windows exe) using the helper script:

```bash
scripts/build_install_promptbench-tui.sh
```

Then launch:

```bash
promptbench tui
```

### TUI Advanced Options

From the setup screen press `a` to open advanced options. You can override:

- output directory
- job limit
- concurrency
- provider list

Press `s` to save the overrides to a sidecar file: `<config>.tui.json`. This file is reloaded automatically when you select the same config.

## Development

### Running Tests

```bash
uv run pytest tests/
```

## License

MIT


## Links discovered
- [uv](https://github.com/astral-sh/uv)
- [config example](https://github.com/AcidicSoil/promptbench/blob/main/demo_config.toml)

--- scripts/skill_context_router.py ---
#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import os
from pathlib import Path

from promptbench.utils.skill_context import resolve_context


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Resolve nested skill context and scenario attachments.")
    parser.add_argument("--skills", default="", help="Comma-separated skill names or paths")
    parser.add_argument("--skills-dir", default="", help="Override skills root (default: ~/.codex/skills or $CODEX_SKILLS_DIR)")
    parser.add_argument("--context-map", default="", help="Optional JSON/TOML context map file")
    parser.add_argument("--category", default="", help="Category selector for context map")
    parser.add_argument("--reference", default="", help="Reference selector for context map")
    parser.add_argument("--horizon", default="", help="Horizon selector for context map")
    parser.add_argument("--tags", default="", help="Comma-separated tags for context map")
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    skills_root = args.skills_dir or os.environ.get("CODEX_SKILLS_DIR", "~/.codex/skills")
    skills_root_path = Path(skills_root).expanduser()
    selectors = [s.strip() for s in args.skills.split(",") if s.strip()]
    context_map = Path(args.context_map).expanduser() if args.context_map else None
    tags = [t.strip() for t in args.tags.split(",") if t.strip()]

    resolution = resolve_context(
        selectors,
        skills_root_path,
        context_map=context_map,
        category=args.category or None,
        reference=args.reference or None,
        horizon=args.horizon or None,
        tags=tags,
    )

    payload = {
        "skills": [{"name": skill.name, "path": str(skill.path)} for skill in resolution.skills],
        "context_files": [str(path) for path in resolution.context_files],
        "nested_files": [str(path) for path in resolution.nested_files],
        "scenario_files": [str(path) for path in resolution.scenario_files],
        "missing": resolution.missing,
    }
    print(json.dumps(payload, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


--- skills/when-to-use-this-skill.md ---

Produce “when to use this skill” documentation + examples for developers, aligned to the Agent Skills concept.

References (must be cited/derived from these pages only):

- <https://developers.openai.com/codex/skills/>
- <https://agentskills.io/home>

Output structure (markdown):

1) Summary (2–4 bullets): what this skill is for and what it improves
2) Optimal usage moments: map to lifecycle phases
3) Example library (minimum 12 examples), presented as a table with columns:
   - Phase
   - Goal
   - Inputs required (files, constraints, audience, format)
   - Raw prompt (before)
   - Optimized prompt (after) — include placeholders like {context}, {inputs}, {constraints}, {acceptance_criteria}, {format}, {deadline}
   - Acceptance criteria (how to judge the optimized prompt is “good”)
4) Non-goals / anti-patterns: 5 cases where the skill should NOT be used

Rules:

- Keep tone developer-neutral and execution-oriented.
- Use realistic SDLC phases and include at least 2 examples per major phase.


--- .tickets/lm-studio/LM-Studio-Model-Selection.md ---
Title:

* Expose full LM Studio sampling parameters in promptbench LMStudioAdapter and tune defaults for deterministic vs creative runs

Summary:

* The user wants the optimal LM Studio model selection and inference parameters for running promptbench.

    LM Studio Model Selection

* Current promptbench LM Studio integration appears to only pass `temperature` and `ttl`, limiting the ability to run reproducible benchmarks or controlled-variability runs using `top_p`, `top_k`, `repeat_penalty`, `seed`, `max_tokens`, and `stop`.

    LM Studio Model Selection

* The request also highlights runtime stability concerns (model auto-eviction via low TTL) for longer promptbench runs.

    LM Studio Model Selection

Background / Context:

* User asked: “optimal lm-studio model and model parameters for runs using promptbench?” and referenced LM Studio docs.

    LM Studio Model Selection

* The assistant response indicates promptbench’s current config points at `qwen_qwen3-vl-4b-instruct`, with a recommended upgrade to `qwen3-vl-8b-instruct`.

    LM Studio Model Selection

* The assistant also states the current `LMStudioAdapter` sends only `temperature` and `ttl`, and uses a system-only message with a `"user '.'"` fallback on HTTP 400.

    LM Studio Model Selection

Current Behavior (Actual):

* LM Studio provider requests from promptbench only include:

  * `temperature`

  * `ttl` (noted as currently set to `60` seconds)

        LM Studio Model Selection

* Advanced inference controls supported by LM Studio’s OpenAI-compatible endpoint are not passed through by promptbench (as described): `top_p`, `top_k`, `repeat_penalty`, `seed`, `max_tokens`, `stop`, etc.

    LM Studio Model Selection

* Low `ttl` may cause unload/reload churn if there are gaps between jobs.

    LM Studio Model Selection

Expected Behavior:

* Promptbench should allow configuring and sending LM Studio inference parameters beyond `temperature`/`ttl`, including at least: `top_p`, `top_k`, `repeat_penalty`, `seed`, `max_tokens`, `stop` (and optionally `presence_penalty`, `frequency_penalty`).

    LM Studio Model Selection

* Provide a clear recommended default model choice and parameter presets for:

  * Deterministic / reproducible benchmark runs

  * Higher-variability / creative exploration runs

        LM Studio Model Selection

* Reduce model eviction risk during long runs by increasing `ttl` (recommended `3600+`) or supporting omission of TTL when models are preloaded.

    LM Studio Model Selection

Requirements:

* Model recommendation (from the user’s installed LM Studio set, per assistant response):

  * Preferred: `qwen3-vl-8b-instruct`

  * Alternative: `essentialai_rnj-1-instruct (Gemma3 ~8.3B)`

  * Throughput option: `mistralai_ministral-3-3b-instruct-2512`

  * Avoid “thinking/reasoning” variants unless explicitly desired and constrained to “final answer only.”

        LM Studio Model Selection

* Add/enable parameter passthrough in `promptbench/providers/lmstudio.py` so `ProviderSpec` can pass: `top_p`, `top_k`, `repeat_penalty`, `seed`, \`max\_tokens


--- payloads/haiku.md ---
Write a haiku about a rusty robot in a garden.


--- payloads/transcript-cleaner_v2.md ---
SYSTEM: Conversation-to-Issue-Ticket Converter

Role

- Convert an exported user↔assistant conversation into a single cleaned, coherent issue ticket.
- Preserve all key details. Remove noise. Do not add new facts.

Input

- An exported conversation containing alternating messages from “user” and “assistant” (any format: plain text, markdown, JSON-like, timestamps optional).

Core rules

- Fidelity: Do not invent requirements, causes, decisions, timelines, metrics, or outcomes.
- Completeness: Keep every materially relevant detail (goals, constraints, edge cases, decisions, rejected options, action items, dependencies, risks).
- No questions: Do not ask the reader for missing info. If information is missing, mark it explicitly as “Unknown” or “Not provided”.
- De-duplication: Merge repeats and restatements. Keep the clearest formulation.
- Conflict handling: If the conversation contradicts itself, report both versions and attribute them (User vs Assistant). Do not resolve by guessing.
- Terminology: Normalize names and terms (features, components, people, systems) using the most consistent wording from the conversation.
- Traceability: When a detail is critical or ambiguous, include a short attributed quote fragment (≤25 words) or “(per user)” / “(per assistant)”.
- Security/privacy: Keep secrets out. If the conversation includes credentials or sensitive personal data, redact and note “[REDACTED]”.
- Output only the issue ticket. No meta-commentary.

Process

1) Parse and segment the conversation into: problem statement(s), context, requirements, constraints, environment, attempted fixes, errors, decisions, next steps.
2) Identify:
   - Primary issue
   - Secondary issues (if present)
   - Stakeholders/owners (if stated)
   - Target system/component
   - Impact and urgency signals
3) Extract concrete artifacts:
   - Steps to reproduce
   - Expected vs actual behavior
   - Error messages/logs
   - Links, IDs, filenames, code snippets (lightly cleaned; preserve meaning)
4) Produce one consolidated, coherent narrative and a structured ticket.

Output format (strict)
Title:

- Concise, specific, action-oriented. Keep short.

Summary:

- 2–5 sentences describing what’s wrong / needed, who is affected, and why it matters.

Background / Context:

- Relevant history and constraints from the conversation.

Current Behavior (Actual):

- Bullet list. Include symptoms, observed outputs, error text.

Expected Behavior:

- Bullet list. Clear success definition.

Requirements:

- Bullet list of explicit requirements extracted from the conversation.
- Include constraints (performance, compatibility, compliance, UX, scope limits).

Out of Scope:

- Bullet list of exclusions stated or implied by the conversation. If none, “Not provided”.

Reproduction Steps:

- Numbered steps. If not available, “Not provided”.

Environment:

- OS, app version, browser, device, deployment, flags, configs. Use “Unknown” when missing.

Evidence:

- Logs/errors (verbatim), screenshots/attachments references, links, file paths, IDs.

Decisions / Agreements:

- Bullet list of decisions made in the conversation, with attribution where needed.

Open Items / Unknowns:

- Bullet list of missing info that blocks execution. No questions; just state unknowns.

Risks / Dependencies:

- Bullet list of dependencies, integrations, approvals, or known risks mentioned.

Acceptance Criteria:

- Testable checklist statements. Derive from requirements and expected behavior.
- If requirements are vague, translate into minimal testable criteria without adding new scope.

Priority & Severity (if inferable from text):

- Priority: P0–P3
- Severity: S0–S3
- Only infer if conversation provides clear cues; otherwise “Not provided”.

Labels (optional):

- 3–8 tags (e.g., bug, enhancement, auth, ui, performance). Only if supported by conversation.

Style constraints

- Use crisp bullet points. No filler.
- Prefer concrete nouns/verbs over abstract phrasing.
- Keep ticket self-contained and understandable without reading the conversation.


--- promptbench/cli.py ---
import argparse
import sys
import os
import json
import shutil
import signal
import subprocess
import time
from datetime import datetime, timezone
from pathlib import Path

from promptbench.core.config import load_config, apply_overrides
from promptbench.core.discovery import discover_files_cached
from promptbench.core.events import EventType, RunEvent, emit_event
from promptbench.core.sets import discover_sets, resolve_selections
from promptbench.core.validation import diagnostics_payload, validate_workspace
from promptbench.core.workspace import ensure_workspace_structure, ensure_set_dirs, workspace_paths
from promptbench.runner.matrix import build_jobs
from promptbench.runner.executor import run_jobs
from promptbench.runner.eventing_executor import run_jobs_with_events
from promptbench.runner.summary import build_summary, write_summary
from promptbench.providers.lmstudio import LMStudioAdapter
from promptbench.providers.codex_cli import CodexCLIAdapter
from promptbench.providers.gemini_cli import GeminiCLIAdapter


def _should_allow_outside_root(globs, base_dir: Path) -> bool:
    for pattern in globs:
        if not pattern:
            continue
        expanded = Path(pattern).expanduser()
        candidate = expanded if expanded.is_absolute() else (base_dir / expanded)
        try:
            if not candidate.resolve().is_relative_to(base_dir):
                return True
        except ValueError:
            return True
    return False


def _resolve_workspace_glob(value: str | None, base_dir: Path, category: str) -> str | None:
    if not value:
        return value
    raw = value.strip()
    if raw in ("@workspace", "workspace"):
        paths = workspace_paths(base_dir)
        target = getattr(paths, category, None)
        if target is None:
            return value
        return str(target / "**/*")
    if raw.startswith("workspace:"):
        _, _, name = raw.partition(":")
        if name.strip() == category:
            paths = workspace_paths(base_dir)
            target = getattr(paths, category, None)
            if target is None:
                return value
            return str(target / "**/*")
    return value


def _build_run_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="promptbench - Systematic prompt benchmark runner")
    parser.add_argument("--config", "-c", required=True, help="Path to TOML config file")
    parser.add_argument("--output", "-o", help="Override output root directory")
    parser.add_argument("--limit", "-l", type=int, help="Limit number of jobs to run")
    parser.add_argument("--concurrency", type=int, help="Number of concurrent jobs")
    parser.add_argument("--providers", help="Comma-separated list of provider IDs to run")
    parser.add_argument("--templates", help="Comma-separated list of template files to run")
    parser.add_argument("--payloads", help="Comma-separated list of payload files to run")
    parser.add_argument("--skills", help="Comma-separated list of skill files to run")
    parser.add_argument("--event-log", help="Write JSONL event stream to file, or '-' for stdout")
    parser.add_argument("--force-refresh", action="store_true", help="Force discovery refresh (ignore cache)")
    return parser


def _emit_run_event(event_sink, run_id, event_type, job_id="", provider_id="", payload=None):
    emit_event(
        event_sink,
        RunEvent(
            ts=datetime.now(timezone.utc),
            type=event_type,
            run_id=run_id,
            job_id=job_id,
            provider_id=provider_id,
            payload=payload or {},
        ),
    )


def _run_benchmark(argv) -> int:
    parser = _build_run_parser()
    args = parser.parse_args(argv)

    event_sink = None
    run_id = None
    log_stream = sys.stdout

    try:
        # 1. Load config
        if not os.path.exists(args.config):
            print(f"Error: Config file not found: {args.config}", file=sys.stderr)
            return 1

        config = load_config(args.config)

        # 2. Apply overrides
        overrides = {}
        if args.output:
            overrides["output_root"] = args.output
        if args.concurrency:
            overrides["concurrency"] = args.concurrency

        config = apply_overrides(config, overrides)

        use_eventing = config.use_eventing_executor or bool(args.event_log)
        if use_eventing:
            if args.event_log:
                if args.event_log == "-":
                    event_sink = sys.stdout
                else:
                    event_sink = open(args.event_log, "w", encoding="utf-8")
            else:
                event_sink = sys.stdout
            run_id = f"run-{int(time.time())}"

        log_stream = sys.stderr if event_sink is sys.stdout else sys.stdout

        if event_sink:
            _emit_run_event(
                event_sink,
                run_id,
                EventType.CONFIG_LOADED,
                payload={"config": args.config},
            )

        # 3. Discover files
        config_dir = Path(args.config).resolve().parent
        config.templates_glob = _resolve_workspace_glob(config.templates_glob, config_dir, "templates")
        config.payloads_glob = _resolve_workspace_glob(config.payloads_glob, config_dir, "payloads")
        if config.skills_glob:
            config.skills_glob = _resolve_workspace_glob(config.skills_glob, config_dir, "skills")

        templates = discover_files_cached(
            [config.templates_glob],
            base_dir=config_dir,
            allow_outside_root=_should_allow_outside_root([config.templates_glob], config_dir),
            force_refresh=args.force_refresh,
        )
        payloads = discover_files_cached(
            [config.payloads_glob],
            base_dir=config_dir,
            allow_outside_root=_should_allow_outside_root([config.payloads_glob], config_dir),
            force_refresh=args.force_refresh,
        )
        skills = []
        if config.skills_glob:
            skills = discover_files_cached(
                [config.skills_glob],
                base_dir=config_dir,
                allow_outside_root=_should_allow_outside_root([config.skills_glob], config_dir),
                force_refresh=args.force_refresh,
            )

        if args.templates:
            templates = _parse_input_list(args.templates, config_dir, category="templates")
        if args.payloads:
            payloads = _parse_input_list(args.payloads, config_dir, category="payloads")
        if args.skills:
            skills = _parse_input_list(args.skills, config_dir, category="skills")

        if event_sink:
            _emit_run_event(
                event_sink,
                run_id,
                EventType.DISCOVERY_COMPLETE,
                payload={
                    "templates": len(templates),
                    "payloads": len(payloads),
                    "skills": len(skills),
                    "providers": len(config.providers),
                },
            )

        # 4. Build jobs
        provider_filter = args.providers.split(",") if args.providers else None
        jobs = build_jobs(
            templates,
            payloads,
            skills,
            config.providers,
            provider_filter=provider_filter,
            limit=args.limit,
            base_dir=str(config_dir),
        )

        if not jobs:
            print("No jobs to run.", file=log_stream)
            return 0

        print(f"Executing {len(jobs)} jobs...", file=log_stream)

        # 5. Initialize adapters
        adapters = {}
        for p in config.providers:
            if p.type == "lmstudio":
                adapters[p.id] = LMStudioAdapter(p)
            elif p.type == "codex_cli":
                adapters[p.id] = CodexCLIAdapter(p)
            elif p.type == "gemini_cli":
                adapters[p.id] = GeminiCLIAdapter(p)
            else:
                print(f"Warning: Unknown provider type '{p.type}' for provider '{p.id}'")

        # 6. Run jobs
        if event_sink:
            results = run_jobs_with_events(jobs, config, adapters, event_sink, run_id)
        else:
            results = run_jobs(jobs, config, adapters)

        # 7. Cleanup / Unload models
        print("Cleaning up resources...", file=log_stream)
        for adapter_id, adapter in adapters.items():
            try:
                adapter.unload()
            except Exception as e:
                print(f"Warning: Failed to unload provider '{adapter_id}': {e}", file=log_stream)

        # 8. Generate summary
        summary = build_summary(config, results)
        summary_path = os.path.join(config.output_root, "summary.json")
        write_summary(summary, summary_path)

        print(f"Done! Summary written to {summary_path}", file=log_stream)
        print(f"Stats: {summary['stats']['ok']} ok, {summary['stats']['error']} error", file=log_stream)

        return 0

    except Exception as e:
        print(f"Unexpected error: {e}", file=sys.stderr)
        return 1
    finally:
        if event_sink and event_sink is not sys.stdout:
            event_sink.close()


def _discover(argv) -> int:
    parser = argparse.ArgumentParser(description="promptbench discover - preflight input discovery")
    parser.add_argument("--config", "-c", required=True, help="Path to TOML config file")
    parser.add_argument("--force-refresh", action="store_true", help="Force discovery refresh (ignore cache)")
    args = parser.parse_args(argv)

    if not os.path.exists(args.config):
        print(f"Error: Config file not found: {args.config}", file=sys.stderr)
        return 1

    config = load_config(args.config)

    config_dir = Path(args.config).resolve().parent
    config.templates_glob = _resolve_workspace_glob(config.templates_glob, config_dir, "templates")
    config.payloads_glob = _resolve_workspace_glob(config.payloads_glob, config_dir, "payloads")
    if config.skills_glob:
        config.skills_glob = _resolve_workspace_glob(config.skills_glob, config_dir, "skills")

    templates = discover_files_cached(
        [config.templates_glob],
        base_dir=config_dir,
        allow_outside_root=_should_allow_outside_root([config.templates_glob], config_dir),
        force_refresh=args.force_refresh,
    )
    payloads = discover_files_cached(
        [config.payloads_glob],
        base_dir=config_dir,
        allow_outside_root=_should_allow_outside_root([config.payloads_glob], config_dir),
        force_refresh=args.force_refresh,
    )
    skills = []
    if config.skills_glob:
        skills = discover_files_cached(
            [config.skills_glob],
            base_dir=config_dir,
            allow_outside_root=_should_allow_outside_root([config.skills_glob], config_dir),
            force_refresh=args.force_refresh,
        )

    skills_count = len(skills) if skills else 0
    effective_skills = skills_count or 1
    jobs_count = len(templates) * len(payloads) * effective_skills * len(config.providers)

    payload = {
        "templates": len(templates),
        "payloads": len(payloads),
        "skills": skills_count,
        "providers": len(config.providers),
        "jobs": jobs_count,
        "output_root": config.output_root,
        "workspace": diagnostics_payload(config_dir),
    }

    print(json.dumps(payload))
    return 0


def _matrix(argv) -> int:
    parser = argparse.ArgumentParser(description="promptbench matrix - list inputs for TUI selection")
    parser.add_argument("--config", "-c", required=True, help="Path to TOML config file")
    parser.add_argument("--force-refresh", action="store_true", help="Force discovery refresh (ignore cache)")
    args = parser.parse_args(argv)

    if not os.path.exists(args.config):
        print(f"Error: Config file not found: {args.config}", file=sys.stderr)
        return 1

    config = load_config(args.config)
    config_dir = Path(args.config).resolve().parent

    config.templates_glob = _resolve_workspace_glob(config.templates_glob, config_dir, "templates")
    config.payloads_glob = _resolve_workspace_glob(config.payloads_glob, config_dir, "payloads")
    if config.skills_glob:
        config.skills_glob = _resolve_workspace_glob(config.skills_glob, config_dir, "skills")

    templates = discover_files_cached(
        [config.templates_glob],
        base_dir=config_dir,
        allow_outside_root=_should_allow_outside_root([config.templates_glob], config_dir),
        force_refresh=args.force_refresh,
    )
    payloads = discover_files_cached(
        [config.payloads_glob],
        base_dir=config_dir,
        allow_outside_root=_should_allow_outside_root([config.payloads_glob], config_dir),
        force_refresh=args.force_refresh,
    )
    skills = []
    if config.skills_glob:
        skills = discover_files_cached(
            [config.skills_glob],
            base_dir=config_dir,
            allow_outside_root=_should_allow_outside_root([config.skills_glob], config_dir),
            force_refresh=args.force_refresh,
        )

    providers = [
        {"id": p.id, "type": p.type, "model": p.model, "url": p.url}
        for p in config.providers
    ]

    template_sets = [s.__dict__ for s in discover_sets(config_dir, "templates")]
    payload_sets = [s.__dict__ for s in discover_sets(config_dir, "payloads")]
    skill_sets = [s.__dict__ for s in discover_sets(config_dir, "skills")]

    skills_count = len(skills) if skills else 0
    effective_skills = skills_count or 1
    jobs_count = len(templates) * len(payloads) * effective_skills * len(config.providers)

    payload = {
        "templates": templates,
        "payloads": payloads,
        "skills": skills,
        "template_sets": template_sets,
        "payload_sets": payload_sets,
        "skill_sets": skill_sets,
        "providers": providers,
        "output_root": config.output_root,
        "jobs": jobs_count,
    }

    print(json.dumps(payload))
    return 0


def _init_workspace(argv) -> int:
    parser = argparse.ArgumentParser(description="promptbench init - scaffold workspace")
    parser.add_argument("--path", help="Workspace base directory (default: cwd)")
    args = parser.parse_args(argv)

    base_dir = Path(args.path).expanduser().resolve() if args.path else Path.cwd().resolve()
    paths = ensure_workspace_structure(base_dir)
    ensure_set_dirs(base_dir, ["templates", "payloads", "skills"])

    sample_template = paths.templates / "example.md"
    if not sample_template.exists():
        sample_template.write_text("You are a helpful assistant.\n\n{{payload}}\n\n{{skill}}\n", encoding="utf-8")

    sample_payload = paths.payloads / "example.md"
    if not sample_payload.exists():
        sample_payload.write_text("Write a short greeting for the user.\n", encoding="utf-8")

    sample_skill = paths.skills / "example.md"
    if not sample_skill.exists():
        sample_skill.write_text("Keep responses under 50 words.\n", encoding="utf-8")

    template_set = paths.sets / "templates" / "default.txt"
    if not template_set.exists():
        template_set.write_text("templates/example.md\n", encoding="utf-8")

    payload_set = paths.sets / "payloads" / "default.txt"
    if not payload_set.exists():
        payload_set.write_text("payloads/example.md\n", encoding="utf-8")

    skill_set = paths.sets / "skills" / "default.txt"
    if not skill_set.exists():
        skill_set.write_text("skills/example.md\n", encoding="utf-8")

    print(f"Workspace ready at {paths.root}")
    return 0


def _doctor(argv) -> int:
    parser = argparse.ArgumentParser(description="promptbench doctor - validate workspace")
    parser.add_argument("--path", help="Workspace base directory (default: cwd)")
    parser.add_argument("--config", help="Path to TOML config file (uses its directory)")
    parser.add_argument("--json", action="store_true", help="Emit JSON diagnostics")
    args = parser.parse_args(argv)

    if args.config:
        base_dir = Path(args.config).expanduser().resolve().parent
    elif args.path:
        base_dir = Path(args.path).expanduser().resolve()
    else:
        base_dir = Path.cwd().resolve()

    diagnostics = validate_workspace(base_dir)
    has_errors = any(d.level == "error" for d in diagnostics)

    if args.json:
        print(json.dumps([d.__dict__ for d in diagnostics]))
    else:
        if not diagnostics:
            print("Workspace OK.")
        for diag in diagnostics:
            level = diag.level.upper()
            hint = f" (hint: {diag.hint})" if diag.hint else ""
            print(f"{level}: {diag.message}{hint}")

    return 1 if has_errors else 0


def _find_tui_binary() -> str | None:
    path = shutil.which("promptbench-tui")
    if path:
        return path

    local_candidate = Path(__file__).resolve().parent / "promptbench-tui"
    if local_candidate.exists() and local_candidate.is_file():
        return str(local_candidate)

    repo_candidate = Path(__file__).resolve().parents[1] / "tui" / "bin" / "promptbench-tui"
    if repo_candidate.exists() and repo_candidate.is_file():
        return str(repo_candidate)

    return None


def _run_tui(argv) -> int:
    binary = _find_tui_binary()
    if not binary:
        print("Error: promptbench-tui binary not found in PATH or repo.", file=sys.stderr)
        return 1

    proc = subprocess.Popen([binary] + argv)
    try:
        proc.wait()
    except KeyboardInterrupt:
        try:
            proc.send_signal(signal.SIGINT)
        except Exception:
            pass
        proc.wait()

    return proc.returncode or 0


def main():
    if len(sys.argv) > 1:
        cmd = sys.argv[1]
        if cmd == "discover":
            sys.exit(_discover(sys.argv[2:]))
        if cmd == "matrix":
            sys.exit(_matrix(sys.argv[2:]))
        if cmd == "init":
            sys.exit(_init_workspace(sys.argv[2:]))
        if cmd == "doctor":
            sys.exit(_doctor(sys.argv[2:]))
        if cmd == "tui":
            sys.exit(_run_tui(sys.argv[2:]))

    sys.exit(_run_benchmark(sys.argv[1:]))


if __name__ == "__main__":
    main()


def _parse_input_list(raw: str, base_dir: Path, category: str | None = None) -> list[str]:
    items = [item.strip() for item in raw.split(",") if item.strip()]
    if category:
        return resolve_selections(items, category, base_dir)
    resolved = []
    for item in items:
        path = Path(item).expanduser()
        if not path.is_absolute():
            path = (base_dir / path).resolve()
        resolved.append(str(path))
    return resolved


--- promptbench/__init__.py ---


--- promptbench/__main__.py ---
from .cli import main

if __name__ == '__main__':
    main()

--- promptbench/providers/base.py ---
from abc import ABC, abstractmethod
from dataclasses import dataclass
from promptbench.core.types import ProviderSpec, JobSpec, Config, ProviderResponse

@dataclass
class ProviderRunContext:
    composed_system: str
    config: Config
    job: JobSpec

class ProviderAdapter(ABC):
    def __init__(self, spec: ProviderSpec):
        self.spec = spec

    @abstractmethod
    def run(self, context: ProviderRunContext) -> ProviderResponse:
        """
        Execute the provider with the given context and return a standardized response.
        """
        pass

    def unload(self) -> bool:
        """
        (Optional) Cleanup or unload the model from memory.
        Returns True if successful or not needed.
        """
        return True

--- promptbench/providers/codex_cli.py ---
import subprocess
import os
from promptbench.providers.base import ProviderAdapter, ProviderRunContext
from promptbench.core.types import ProviderResponse

class CodexCLIAdapter(ProviderAdapter):
    def run(self, context: ProviderRunContext) -> ProviderResponse:
        cli_path = self.spec.args[0] if self.spec.args else "codex"
        # Codex exec semantics: PROMPT="-" means read from stdin
        env = os.environ.copy()
        env.update(self.spec.env)
        env["PROMPT"] = "-"
        
        try:
            cmd = [cli_path, "exec"]
            if len(self.spec.args) > 1:
                cmd.extend(self.spec.args[1:])
                
            process = subprocess.Popen(
                cmd,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                env=env
            )
            
            stdout, stderr = process.communicate(input=context.composed_system)
            
            if process.returncode != 0:
                message = stderr.strip() or "Codex CLI returned non-zero exit code"
                return ProviderResponse(
                    stdout=stdout,
                    stderr=stderr,
                    error={
                        "code": "CODEX_CLI_ERROR",
                        "exit_code": process.returncode,
                        "message": message,
                    },
                )

            if not stdout.strip():
                return ProviderResponse(
                    stdout=stdout,
                    stderr=stderr,
                    error={
                        "code": "CODEX_CLI_EMPTY_OUTPUT",
                        "message": "Codex CLI returned empty output",
                    },
                )
            
            return ProviderResponse(
                text=stdout,
                stdout=stdout,
                stderr=stderr,
                meta={"exit_code": process.returncode}
            )
            
        except Exception as e:
            return ProviderResponse(
                error={"code": "CODEX_CLI_EXCEPTION", "message": str(e)}
            )


--- promptbench/core/compose.py ---
from enum import Enum
from typing import Optional
from promptbench.core.types import CompositionMode

class SkillInjectionMode(str, Enum):
    PREFIX = "prefix"
    SUFFIX = "suffix"
    NONE = "none"

def compose_system(
    template_text: str, 
    payload_text: str, 
    skill_text: Optional[str] = None,
    mode: CompositionMode = CompositionMode.MARKER
) -> str:
    """
    Build the composed system prompt. 
    In 'engine' mode, this performs recursive replacement:
    1. If skill exists, replace Skill's last line with 'User prompt: {payload}'.
    2. Replace Template's last line with 'User prompt: {Result of 1}'.
    """
    if mode == CompositionMode.REPLACE_LAST_LINE:
        # Step 1: Fuse Skill and Payload if Skill exists
        if skill_text and skill_text.lower() != "none":
            # The Skill is now the 'Primary' and the Payload is the 'Secondary'
            fused_inner = compose_engine_style(skill_text, payload_text)
            # Step 2: Fuse the House (Template) with the fused inner content
            return compose_engine_style(template_text, fused_inner)
        else:
            # No skill, just fuse House and Payload
            return compose_engine_style(template_text, payload_text)

    # Fallback to older logic for other modes
    effective_payload = payload_text
    if skill_text and skill_text.lower() != "none":
        effective_payload = inject_skill(payload_text, skill_text)

    if mode == CompositionMode.MARKER and "{{USER_PROMPT}}" in template_text:
        return template_text.replace("{{USER_PROMPT}}", effective_payload, 1)
    
    return f"{template_text}\nUser prompt:\n{effective_payload}"

def compose_engine_style(primary_text: str, secondary_text: str) -> str:
    """
    Implements the 'prompt-composition-engine' logic:
    1. Locates the last non-empty line in primary.
    2. Replaces it with 'User prompt: {secondary}'.
    3. Preserves trailing blank lines.
    """
    lines = primary_text.splitlines(keepends=True)
    if not lines:
        return f"User prompt: {secondary_text}"

    # Find the index of the last non-empty/non-whitespace line
    target_idx = -1
    for i in range(len(lines) - 1, -1, -1):
        if lines[i].strip():
            target_idx = i
            break
    
    if target_idx == -1: # All lines are empty or whitespace
        return f"User prompt: {secondary_text}\n" + "".join(lines)

    # Preserve the line ending if it had one
    original_line = lines[target_idx]
    newline = ""
    if original_line.endswith("\r\n"):
        newline = "\r\n"
    elif original_line.endswith("\n"):
        newline = "\n"

    # Replace the target line verbatim as per spec: "User prompt: {S}"
    lines[target_idx] = f"User prompt: {secondary_text}{newline}" 
    
    return "".join(lines)

def inject_skill(payload_text: str, skill_text: str, mode: SkillInjectionMode = SkillInjectionMode.PREFIX) -> str:
    """Standard non-engine skill injection."""
    if not skill_text or skill_text.lower() == "none":
        return payload_text
    delimiter = "\n---\n"
    if mode == SkillInjectionMode.PREFIX:
        return f"{skill_text}{delimiter}{payload_text}"
    elif mode == SkillInjectionMode.SUFFIX:
        return f"{payload_text}{delimiter}{skill_text}"
    else:
        return f"{skill_text}{payload_text}"

--- promptbench/core/config.py ---
import tomllib
import pathlib
from typing import Dict, Any, Optional
from promptbench.core.types import Config, ProviderSpec, CompositionMode, SkillRouterConfig
from promptbench.core.errors import ConfigError

def load_config(path: str) -> Config:
    """Load and validate TOML configuration."""
    try:
        with open(path, "rb") as f:
            data = tomllib.load(f)
    except Exception as e:
        raise ConfigError(f"Failed to parse TOML config: {path}", details={"error": str(e)})

    # Validation
    if "inputs" not in data:
        raise ConfigError("Missing 'inputs' section in config")
    
    inputs = data["inputs"]
    if "templates" not in inputs:
        raise ConfigError("Missing 'inputs.templates' in config")
    if "payloads" not in inputs:
        raise ConfigError("Missing 'inputs.payloads' in config")
    
    if "providers" not in data:
        raise ConfigError("Missing 'providers' section in config")
    if not data["providers"]:
        raise ConfigError("At least one provider must be defined")

    providers = []
    for p_id, p_data in data["providers"].items():
        if "type" not in p_data:
            raise ConfigError(f"Provider '{p_id}' missing 'type'")
        
        providers.append(ProviderSpec(
            id=p_id,
            type=p_data["type"],
            model=p_data.get("model"),
            url=p_data.get("url"),
            args=p_data.get("args", []),
            env=p_data.get("env", {})
        ))

    runner_cfg = data.get("runner", {}) or {}

    # Parse composition mode
    raw_mode = runner_cfg.get("composition_mode", "marker")
    try:
        comp_mode = CompositionMode(raw_mode)
    except ValueError:
        raise ConfigError(f"Invalid composition_mode: {raw_mode}. Expected one of: {[m.value for m in CompositionMode]}")

    router_cfg = data.get("skill_router", {}) or {}
    skill_router = SkillRouterConfig(
        enabled=bool(router_cfg.get("enabled", False)),
        skills_path=router_cfg.get("skills_path"),
        force_skills=list(router_cfg.get("force_skills", [])),
    )

    return Config(
        templates_glob=inputs["templates"],
        payloads_glob=inputs["payloads"],
        skills_glob=inputs.get("skills"),
        output_root=data.get("output", {}).get("root", "runs"),
        providers=providers,
        concurrency=runner_cfg.get("concurrency", 1),
        retries=runner_cfg.get("retries", 0),
        timeout=runner_cfg.get("timeout", 60),
        use_eventing_executor=runner_cfg.get("use_eventing_executor", False),
        composition_mode=comp_mode,
        write_markdown_output=bool(runner_cfg.get("write_markdown_output", False)),
        skill_router=skill_router,
    )

def apply_overrides(config: Config, overrides: Dict[str, Any]) -> Config:
    """Apply CLI overrides to the configuration object."""
    if "output_root" in overrides:
        config.output_root = overrides["output_root"]
    if "concurrency" in overrides:
        config.concurrency = overrides["concurrency"]
    if "retries" in overrides:
        config.retries = overrides["retries"]
    if "timeout" in overrides:
        config.timeout = int(overrides["timeout"])
    return config


--- promptbench/core/constants.py ---
RUN_JSON_SCHEMA_VERSION = 1


--- promptbench/core/contract.py ---
from __future__ import annotations

from typing import Any

from promptbench.core.types import ProviderResponse, ProviderSpec


class ContractViolationError(ValueError):
    pass


def _require_non_empty_string(value: Any, field: str) -> str:
    if not isinstance(value, str) or not value.strip():
        raise ContractViolationError(f"{field} must be a non-empty string")
    return value


def validate_response(response: ProviderResponse, spec: ProviderSpec) -> ProviderResponse:
    if not isinstance(response, ProviderResponse):
        raise ContractViolationError("Provider adapters must return ProviderResponse")

    if response.meta is None:
        response.meta = {}
    if not isinstance(response.meta, dict):
        raise ContractViolationError("ProviderResponse.meta must be a dict")

    response.meta.setdefault("provider_id", spec.id)
    response.meta.setdefault("provider_type", spec.type)

    if response.raw_json is not None and not isinstance(response.raw_json, dict):
        raise ContractViolationError("ProviderResponse.raw_json must be a dict when provided")

    if response.error is not None:
        if not isinstance(response.error, dict):
            raise ContractViolationError("ProviderResponse.error must be a dict when provided")
        _require_non_empty_string(response.error.get("code"), "error.code")
        _require_non_empty_string(response.error.get("message"), "error.message")
    else:
        if not isinstance(response.text, str):
            raise ContractViolationError("ProviderResponse.text must be a string")
        if not response.text.strip():
            raise ContractViolationError("ProviderResponse.text must be non-empty when error is None")

    return response


--- promptbench/core/discovery.py ---
import glob
import pathlib
import os
from typing import List, Optional, Union

from promptbench.core.discovery_cache import CacheManager, cache_key, _entry_is_fresh, _file_mtimes, _root_mtimes


def _is_within_base(path: pathlib.Path, base_dir: pathlib.Path) -> bool:
    try:
        path.resolve().relative_to(base_dir.resolve())
        return True
    except ValueError:
        return False

def discover_files(
    globs: List[str],
    filters: Optional[List[str]] = None,
    base_dir: Optional[Union[str, pathlib.Path]] = None,
    allow_outside_root: bool = False
) -> List[str]:
    """
    Discover files using one or more glob patterns.
    Returns a stable, alphabetically sorted list of file paths.
    Supports tilde (~) expansion for home directories.
    """
    base_path = pathlib.Path(base_dir).resolve() if base_dir else None
    paths = set()
    for pattern in globs:
        # Expand ~ to user home directory
        expanded_pattern = os.path.expanduser(pattern)
        
        # Expand globs recursively if ** is present
        for path in glob.glob(expanded_pattern, recursive=True):
            p = pathlib.Path(path)
            if p.is_file():
                if base_path and not allow_outside_root:
                    if not _is_within_base(p, base_path):
                        raise ValueError(f"Disallowed path outside base_dir: {p} (base_dir={base_path})")
                # Apply filters if provided (e.g., skip hidden files)
                if filters:
                    if any(f in p.name for f in filters):
                        continue
                paths.add(str(p))
    
    return sorted(list(paths))


def discover_files_cached(
    globs: List[str],
    filters: Optional[List[str]] = None,
    base_dir: Optional[Union[str, pathlib.Path]] = None,
    allow_outside_root: bool = False,
    cache: Optional[CacheManager] = None,
    force_refresh: bool = False,
) -> List[str]:
    base_path = pathlib.Path(base_dir).resolve() if base_dir else pathlib.Path.cwd().resolve()
    if cache is None:
        cache = CacheManager(base_path)
    key = cache_key(globs, base_path, filters)
    cache_data = cache.load()
    entry = cache_data.get(key)

    if entry and not force_refresh and _entry_is_fresh(entry, globs, base_path):
        return entry.get("paths", [])

    paths = discover_files(
        globs=globs,
        filters=filters,
        base_dir=base_path,
        allow_outside_root=allow_outside_root,
    )
    cache_data[key] = {
        "paths": paths,
        "mtimes": _file_mtimes(paths),
        "root_mtimes": _root_mtimes(globs, base_path),
    }
    cache.save(cache_data)
    return paths


--- templates/creative_writer.md ---
You are a world-class creative writer.
Your goal is to fulfill the user request with elegance and precision.

{{USER_PROMPT}}

End of response.


--- templates/prompt-composition-engine.md ---
```md
You are an AI prompt-composition engine.

GOAL
Compose a single SYSTEM message by combining a PRIMARY system template with a SECONDARY user request, then return the assistant reply produced when the composed SYSTEM message is used as the only message in a chat completion call.

INPUTS
- PRIMARY.md (string P): a system-prompt template that ends with an instruction line telling the model to optimize the next input.
- SECONDARY.md (string S): the full user request to be optimized.

TASK
1) Read PRIMARY.md as string P (preserve all characters and line breaks).
2) Read SECONDARY.md as string S (preserve all characters and line breaks).
3) In P, locate the final line (the last line of the file). Replace ONLY that final line with exactly:
   User prompt: {S}
   where {S} is the entire contents of SECONDARY.md inserted verbatim (no trimming, no escaping, no wrapping, no summarizing).
   - Keep every prior line of P unchanged.
   - Ensure there is a newline before "User prompt:" if P does not already end with one.
4) Construct SYSTEM = modified P from step 3.
5) Call a chat completion API using ONLY this single message:
   messages = [{ "role": "system", "content": SYSTEM }]
   - Do NOT add user/assistant messages.
   - Do NOT add tool messages.
   - Do NOT add extra instructions.
6) Return the resulting assistant reply EXACTLY as received:
   - No preface, no commentary, no markdown fences, no metadata, no citations.
   - Preserve the reply text verbatim.

VALIDATION / EDGE CASES
- If PRIMARY.md ends with trailing blank lines, treat the last non-empty line as the “final line” to be replaced, and preserve the trailing blank lines after replacement.
- If PRIMARY.md contains multiple occurrences of “User prompt:” or similar, ignore them; only replace the final line as defined above.
- If SECONDARY.md contains triple backticks, XML/JSON, or other delimiters, include them verbatim; do not escape.
- If PRIMARY.md has no newline at EOF, handle cleanly and still produce a valid combined string.

MINIMAL EXAMPLE (SCHEMATIC)
messages: [
  {
    "role": "system",
    "content": "<PRIMARY.md content, unchanged except final line replaced>\nUser prompt: <SECONDARY.md content verbatim>"
  }
]


```

--- templates/prompt-composition-engine_v3.md ---
You are a prompt-composition engine.

GOAL
Given a TEMPLATE (system template) and a PAYLOAD (user request), compose a single SYSTEM message by inserting PAYLOAD into TEMPLATE, then produce the assistant reply that would result if that composed SYSTEM were the only message in the chat.

INPUTS

- TEMPLATE.md (string T): system-prompt template text.
- PAYLOAD.md (string U): the full user request text.

COMPOSITION RULE

- TEMPLATE should contain exactly one insertion marker: {{USER_PROMPT}}
- If the marker exists: replace the first occurrence of {{USER_PROMPT}} with U verbatim.
- If the marker does not exist: append the following to the end of T (ensuring exactly one newline before it):
  User prompt:
  {U}
  where {U} is U verbatim.

TASK

1) Read T exactly (preserve all characters and line breaks).
2) Read U exactly (preserve all characters and line breaks).
3) Construct COMPOSED_SYSTEM using the COMPOSITION RULE.
4) Now act as the assistant with COMPOSED_SYSTEM as your only system message, and produce the response to the embedded user prompt.

OUTPUT
Return ONLY the assistant response text.

- No preface, no commentary, no metadata.
- Preserve the response exactly as generated.

VALIDATION / EDGE CASES

- Do not trim or escape any input.
- If TEMPLATE has trailing blank lines, preserve them.
- If U contains any delimiters (backticks, XML/JSON, etc.), include verbatim.


--- tests/test_artifacts.py ---
import pytest
import pathlib
import json
from promptbench.core.types import ProviderResponse
from promptbench.artifacts.layout import make_run_dir, get_artifact_paths
from promptbench.artifacts.writer import write_run_artifacts

def test_artifact_layout_and_writing(tmp_path):
    root = tmp_path / "runs"
    job_id = "test-job-123"
    
    run_dir = make_run_dir(str(root), job_id)
    paths = get_artifact_paths(run_dir)
    
    assert paths.root == run_dir
    assert job_id in paths.composed_system
    
    resp = ProviderResponse(
        text="Assistant reply",
        meta={"usage": 100},
        raw_json={"full": "data"}
    )
    
    write_run_artifacts(
        paths,
        composed_system="System prompt content",
        provider_response=resp,
        run_meta={"job_id": job_id, "duration": 1.5},
        inputs_manifest={
            "template": {"path": "t.md", "content": "T"},
            "payload": {"path": "p.md", "content": "P"},
            "skill": {"path": None, "selected": []},
        },
        created_at="2026-01-05T00:00:00+00:00",
    )
    
    # Verify files
    assert pathlib.Path(paths.composed_system).read_text() == "System prompt content"
    assert pathlib.Path(paths.output).read_text() == "Assistant reply"
    
    run_json_data = json.loads(pathlib.Path(paths.run_json).read_text())
    assert run_json_data["schema_version"] == 1
    assert run_json_data["meta"]["job_id"] == job_id
    assert run_json_data["provider"]["meta"]["usage"] == 100
    assert run_json_data["created_at"] == "2026-01-05T00:00:00+00:00"
    
    # We now call it output.json
    assert pathlib.Path(paths.output_json).exists()
    output_json_data = json.loads(pathlib.Path(paths.output_json).read_text())
    assert output_json_data == {"full": "data"}

    inputs_json_data = json.loads(pathlib.Path(paths.inputs_json).read_text())
    assert inputs_json_data["template"]["content"] == "T"
    assert inputs_json_data["created_at"] == "2026-01-05T00:00:00+00:00"


--- tests/test_artifacts_concurrency.py ---
import json
from concurrent.futures import ThreadPoolExecutor

from promptbench.core.types import ProviderResponse
from promptbench.artifacts.layout import make_run_dir, get_artifact_paths
from promptbench.artifacts.writer import write_run_artifacts


def _write_artifacts(paths, job_id):
    resp = ProviderResponse(text="ok", meta={"job": job_id})
    write_run_artifacts(
        paths,
        composed_system=f"system-{job_id}",
        provider_response=resp,
        run_meta={"job_id": job_id},
        inputs_manifest={"template": {"path": "t", "content": ""}, "payload": {"path": "p", "content": ""}, "skill": {}},
    )


def test_write_run_artifacts_concurrent_distinct_dirs(tmp_path):
    root = tmp_path / "runs"
    job_ids = [f"job-{i}" for i in range(10)]
    paths_list = []
    for job_id in job_ids:
        run_dir = make_run_dir(str(root), job_id)
        paths_list.append((get_artifact_paths(run_dir), job_id))

    with ThreadPoolExecutor(max_workers=10) as executor:
        futures = [executor.submit(_write_artifacts, paths, job_id) for paths, job_id in paths_list]
        for f in futures:
            f.result()

    for paths, _ in paths_list:
        run_json = json.loads(open(paths.run_json, "r", encoding="utf-8").read())
        output_json = json.loads(open(paths.output_json, "r", encoding="utf-8").read())
        inputs_json = json.loads(open(paths.inputs_json, "r", encoding="utf-8").read())
        assert "meta" in run_json
        assert isinstance(output_json, dict)
        assert "template" in inputs_json


def test_write_run_artifacts_concurrent_same_dir(tmp_path):
    root = tmp_path / "runs"
    run_dir = make_run_dir(str(root), "shared-job")
    paths = get_artifact_paths(run_dir)

    with ThreadPoolExecutor(max_workers=20) as executor:
        futures = [executor.submit(_write_artifacts, paths, f"job-{i}") for i in range(20)]
        for f in futures:
            f.result()

    run_json = json.loads(open(paths.run_json, "r", encoding="utf-8").read())
    output_json = json.loads(open(paths.output_json, "r", encoding="utf-8").read())
    inputs_json = json.loads(open(paths.inputs_json, "r", encoding="utf-8").read())
    assert "meta" in run_json
    assert isinstance(output_json, dict)
    assert "template" in inputs_json


--- tests/test_cli.py ---
import subprocess
import os
import json
from pathlib import Path
from unittest.mock import patch, MagicMock
import sys

def test_cli_smoke_run(tmp_path):
    # Setup directories
    (tmp_path / "templates").mkdir()
    (tmp_path / "payloads").mkdir()
    (tmp_path / "runs").mkdir()
    
    (tmp_path / "templates" / "t1.md").write_text("T: {{USER_PROMPT}}")
    (tmp_path / "payloads" / "p1.md").write_text("P")
    
    config_content = f"""
[inputs]
templates = "{tmp_path}/templates/*.md"
payloads = "{tmp_path}/payloads/*.md"

[output]
root = "{tmp_path}/runs"

[providers.mock]
type = "lmstudio"
url = "http://non-existent"
"""
    config_path = tmp_path / "config.toml"
    config_path.write_text(config_content)
    
    # Run CLI using module mode
    result = subprocess.run(
        ["python3", "-m", "promptbench", "--config", str(config_path)],
        capture_output=True,
        text=True,
        env={**os.environ, "PYTHONPATH": "."}
    )
    
    # The summary should be written even if providers fail
    summary_path = tmp_path / "runs" / "summary.json"
    assert summary_path.exists(), f"Summary file not found. stdout: {result.stdout}, stderr: {result.stderr}"
    
    summary = json.loads(summary_path.read_text())
    assert summary["stats"]["total_jobs"] == 1
    # It should have 1 error because LM Studio non-existent
    assert summary["stats"]["error"] == 1
    assert "Cleaning up resources..." in result.stdout

def test_cli_cleanup_is_called():
    # This is harder to test with subprocess.run without mocking the inner main.
    # We'll just verify the print statement in the smoke test.
    pass

def test_cli_uses_eventing_executor_from_config(tmp_path, monkeypatch):
    from promptbench.core.types import JobSpec, RunResult, RunStatus
    import promptbench.cli as cli

    (tmp_path / "templates").mkdir()
    (tmp_path / "payloads").mkdir()
    (tmp_path / "runs").mkdir()
    (tmp_path / "templates" / "t1.md").write_text("T: {{USER_PROMPT}}")
    (tmp_path / "payloads" / "p1.md").write_text("P")

    config_content = f"""
[inputs]
templates = "{tmp_path}/templates/*.md"
payloads = "{tmp_path}/payloads/*.md"

[output]
root = "{tmp_path}/runs"

[runner]
use_eventing_executor = true

[providers.lms]
type = "lmstudio"
url = "http://localhost:1234"
"""
    config_path = tmp_path / "config.toml"
    config_path.write_text(config_content)

    dummy_job = JobSpec(
        id="job1",
        template_path=str(tmp_path / "templates" / "t1.md"),
        payload_path=str(tmp_path / "payloads" / "p1.md"),
        provider_id="lms",
    )

    called = {}

    def fake_build_jobs(*args, **kwargs):
        return [dummy_job]

    def fake_run_jobs(*args, **kwargs):
        raise AssertionError("run_jobs should not be called when use_eventing_executor is true")

    def fake_run_jobs_with_events(jobs, config, adapters, event_sink, run_id):
        called["event_sink"] = event_sink
        called["run_id"] = run_id
        return [RunResult(status=RunStatus.OK, duration=0.0)]

    class DummyAdapter:
        def __init__(self, spec):
            self.spec = spec
        def unload(self):
            return None

    monkeypatch.setattr(cli, "build_jobs", fake_build_jobs)
    monkeypatch.setattr(cli, "run_jobs", fake_run_jobs)
    monkeypatch.setattr(cli, "run_jobs_with_events", fake_run_jobs_with_events)
    monkeypatch.setattr(cli, "LMStudioAdapter", DummyAdapter)

    exit_code = cli._run_benchmark(["--config", str(config_path)])
    assert exit_code == 0
    assert called.get("event_sink") is sys.stdout
    assert called.get("run_id")


def test_discover_allows_explicit_outside_paths(tmp_path, capsys):
    import promptbench.cli as cli

    external = tmp_path.parent / "external_payloads"
    external.mkdir(parents=True, exist_ok=True)
    (external / "p1.md").write_text("P")

    (tmp_path / "templates").mkdir()
    (tmp_path / "templates" / "t1.md").write_text("T: {{USER_PROMPT}}")

    config_content = f"""
[inputs]
templates = "{tmp_path}/templates/*.md"
payloads = "{external}/*.md"

[providers.mock]
type = "lmstudio"
url = "http://localhost:1234"
"""
    config_path = tmp_path / "config.toml"
    config_path.write_text(config_content)

    exit_code = cli._discover(["--config", str(config_path)])
    assert exit_code == 0
    out = capsys.readouterr().out
    assert "\"payloads\": 1" in out


def test_matrix_lists_inputs(tmp_path, capsys):
    import promptbench.cli as cli

    (tmp_path / "templates").mkdir()
    (tmp_path / "payloads").mkdir()
    (tmp_path / "runs").mkdir()
    (tmp_path / "skills").mkdir()
    (tmp_path / "templates" / "t1.md").write_text("T")
    (tmp_path / "payloads" / "p1.md").write_text("P")
    (tmp_path / "skills" / "s1.md").write_text("S")

    config_content = f"""
[inputs]
templates = "{tmp_path}/templates/*.md"
payloads = "{tmp_path}/payloads/*.md"
skills = "{tmp_path}/skills/*.md"

[providers.mock]
type = "lmstudio"
url = "http://localhost:1234"
"""
    config_path = tmp_path / "config.toml"
    config_path.write_text(config_content)

    exit_code = cli._matrix(["--config", str(config_path)])
    assert exit_code == 0
    out = capsys.readouterr().out
    payload = json.loads(out)
    assert len(payload["templates"]) == 1
    assert len(payload["payloads"]) == 1
    assert len(payload["skills"]) == 1
    assert len(payload["providers"]) == 1


def test_run_uses_explicit_template_payload_list(tmp_path, monkeypatch):
    import promptbench.cli as cli
    from promptbench.core.types import JobSpec, RunResult, RunStatus

    (tmp_path / "templates").mkdir()
    (tmp_path / "payloads").mkdir()
    (tmp_path / "runs").mkdir()
    (tmp_path / "templates" / "t1.md").write_text("T1")
    (tmp_path / "templates" / "t2.md").write_text("T2")
    (tmp_path / "payloads" / "p1.md").write_text("P1")
    (tmp_path / "payloads" / "p2.md").write_text("P2")

    config_content = f"""
[inputs]
templates = "{tmp_path}/templates/*.md"
payloads = "{tmp_path}/payloads/*.md"

[output]
root = "{tmp_path}/runs"

[providers.lms]
type = "lmstudio"
url = "http://localhost:1234"
"""
    config_path = tmp_path / "config.toml"
    config_path.write_text(config_content)

    observed = {}

    def fake_build_jobs(templates, payloads, skills, providers, **kwargs):
        observed["templates"] = list(templates)
        observed["payloads"] = list(payloads)
        return [JobSpec(id="job1", template_path=templates[0], payload_path=payloads[0], provider_id="lms")]

    def fake_run_jobs(*args, **kwargs):
        return [RunResult(status=RunStatus.OK, duration=0.0)]

    class DummyAdapter:
        def __init__(self, spec):
            self.spec = spec
        def unload(self):
            return None

    monkeypatch.setattr(cli, "build_jobs", fake_build_jobs)
    monkeypatch.setattr(cli, "run_jobs", fake_run_jobs)
    monkeypatch.setattr(cli, "LMStudioAdapter", DummyAdapter)

    exit_code = cli._run_benchmark([
        "--config", str(config_path),
        "--templates", str(tmp_path / "templates" / "t2.md"),
        "--payloads", str(tmp_path / "payloads" / "p2.md"),
    ])
    assert exit_code == 0
    assert observed["templates"] == [str(tmp_path / "templates" / "t2.md")]
    assert observed["payloads"] == [str(tmp_path / "payloads" / "p2.md")]


--- tests/test_cli_providers.py ---
import pytest
from unittest.mock import patch, MagicMock
from promptbench.core.types import ProviderSpec, JobSpec, Config
from promptbench.providers.base import ProviderRunContext
from promptbench.providers.codex_cli import CodexCLIAdapter
from promptbench.providers.gemini_cli import GeminiCLIAdapter

def test_codex_cli_success():
    spec = ProviderSpec(id="codex", type="codex_cli", args=["/usr/local/bin/codex"])
    adapter = CodexCLIAdapter(spec)
    
    ctx = ProviderRunContext(
        composed_system="Composed prompt",
        config=Config(templates_glob="", payloads_glob="", output_root="", providers=[]),
        job=JobSpec(id="j1", template_path="", payload_path="", provider_id="codex")
    )
    
    with patch("subprocess.Popen") as mock_popen:
        mock_proc = MagicMock()
        mock_proc.communicate.return_value = ("Success output", "")
        mock_proc.returncode = 0
        mock_popen.return_value = mock_proc
        
        resp = adapter.run(ctx)
        assert resp.text == "Success output"
        assert resp.error is None
        # Verify it called 'exec'
        assert mock_popen.call_args[0][0] == ["/usr/local/bin/codex", "exec"]

def test_gemini_cli_json_success():
    spec = ProviderSpec(id="gemini", type="gemini_cli", args=["gemini", "--output-format", "json"])
    adapter = GeminiCLIAdapter(spec)
    
    ctx = ProviderRunContext(
        composed_system="Prompt",
        config=Config(templates_glob="", payloads_glob="", output_root="", providers=[]),
        job=JobSpec(id="j1", template_path="", payload_path="", provider_id="gemini")
    )
    
    with patch("subprocess.Popen") as mock_popen:
        mock_proc = MagicMock()
        mock_proc.communicate.return_value = ('{"text": "Parsed output"}', "")
        mock_proc.returncode = 0
        mock_popen.return_value = mock_proc
        
        resp = adapter.run(ctx)
        assert resp.text == "Parsed output"
        assert resp.raw_json["text"] == "Parsed output"

def test_cli_failure_capture():
    spec = ProviderSpec(id="codex", type="codex_cli")
    adapter = CodexCLIAdapter(spec)
    
    ctx = ProviderRunContext(
        composed_system="Prompt",
        config=Config(templates_glob="", payloads_glob="", output_root="", providers=[]),
        job=JobSpec(id="j1", template_path="", payload_path="", provider_id="codex")
    )
    
    with patch("subprocess.Popen") as mock_popen:
        mock_proc = MagicMock()
        mock_proc.communicate.return_value = ("Partial output", "Fatal error")
        mock_proc.returncode = 1
        mock_popen.return_value = mock_proc
        
        resp = adapter.run(ctx)
        assert resp.error is not None
        assert resp.error["exit_code"] == 1
        assert resp.error["message"] == "Fatal error"


--- tests/test_compose.py ---
import pytest
from promptbench.core.compose import compose_system, inject_skill, SkillInjectionMode

def test_compose_marker_replacement():
    template = "System instructions.\n{{USER_PROMPT}}\nEnd instructions."
    payload = "Do this."
    expected = "System instructions.\nDo this.\nEnd instructions."
    assert compose_system(template, payload) == expected

def test_compose_marker_replacement_first_only():
    template = "{{USER_PROMPT}} again {{USER_PROMPT}}"
    payload = "FIRST"
    expected = "FIRST again {{USER_PROMPT}}"
    assert compose_system(template, payload) == expected

def test_compose_append_logic():
    template = "System instructions."
    payload = "Do this."
    result = compose_system(template, payload)
    assert result == "System instructions.\nUser prompt:\nDo this."

def test_compose_preserves_trailing_blank_lines():
    template = "Instructions.\n\n"
    payload = "Payload."
    result = compose_system(template, payload)
    # The \n in the f-string adds one newline
    assert result == "Instructions.\n\n\nUser prompt:\nPayload."

def test_inject_skill_prefix():
    payload = "User query."
    skill = "Search skill."
    result = inject_skill(payload, skill, mode=SkillInjectionMode.PREFIX)
    assert result == "Search skill.\n---\nUser query."

def test_inject_skill_none():
    payload = "User query."
    assert inject_skill(payload, "none") == payload
    assert inject_skill(payload, "") == payload


--- tests/test_config.py ---
import pytest
import pathlib
from promptbench.core.config import load_config, apply_overrides
from promptbench.core.errors import ConfigError

def test_load_valid_config(tmp_path):
    config_content = """
    [inputs]
    templates = "templates/*.md"
    payloads = "payloads/*.md"
    skills = "skills/*.md"

    [output]
    root = "custom_runs"

    [runner]
    concurrency = 5
    retries = 2
    use_eventing_executor = true
    write_markdown_output = true

    [skill_router]
    enabled = true
    skills_path = "~/.codex/skills"
    force_skills = ["python"]

    [providers.lms]
    type = "lmstudio"
    model = "gpt-4"
    url = "http://localhost:1234"
    """
    config_file = tmp_path / "config.toml"
    config_file.write_text(config_content)
    
    config = load_config(str(config_file))
    
    assert config.templates_glob == "templates/*.md"
    assert config.output_root == "custom_runs"
    assert config.concurrency == 5
    assert config.use_eventing_executor is True
    assert config.write_markdown_output is True
    assert config.skill_router.enabled is True
    assert config.skill_router.skills_path == "~/.codex/skills"
    assert config.skill_router.force_skills == ["python"]
    assert len(config.providers) == 1
    assert config.providers[0].id == "lms"

def test_load_invalid_config(tmp_path):
    # Missing required inputs
    config_content = """
    [providers.lms]
    type = "lmstudio"
    """
    config_file = tmp_path / "config.toml"
    config_file.write_text(config_content)
    
    with pytest.raises(ConfigError) as excinfo:
        load_config(str(config_file))
    assert "Missing 'inputs' section" in str(excinfo.value)

def test_apply_overrides():
    from promptbench.core.types import Config
    config = Config(templates_glob="t", payloads_glob="p", output_root="old", providers=[])
    
    apply_overrides(config, {"output_root": "new", "concurrency": 10})
    
    assert config.output_root == "new"
    assert config.concurrency == 10


--- tests/test_contract.py ---
import json
import pathlib

import pytest

from promptbench.core.contract import ContractViolationError, validate_response
from promptbench.core.types import Config, JobSpec, ProviderResponse, ProviderSpec, RunStatus
from promptbench.providers.base import ProviderAdapter, ProviderRunContext
from promptbench.runner import executor


def _make_job(tmp_path: pathlib.Path) -> JobSpec:
    templates = tmp_path / "templates"
    payloads = tmp_path / "payloads"
    templates.mkdir()
    payloads.mkdir()
    t_path = templates / "t.md"
    p_path = payloads / "p.md"
    t_path.write_text("T: {{USER_PROMPT}}")
    p_path.write_text("P")
    return JobSpec(
        id="job1",
        template_path=str(t_path),
        payload_path=str(p_path),
        provider_id="stub",
    )


def test_validate_response_injects_provider_meta():
    spec = ProviderSpec(id="p1", type="stub")
    response = validate_response(ProviderResponse(text="ok"), spec)
    assert response.meta["provider_id"] == "p1"
    assert response.meta["provider_type"] == "stub"


def test_validate_response_rejects_empty_text():
    spec = ProviderSpec(id="p1", type="stub")
    with pytest.raises(ContractViolationError):
        validate_response(ProviderResponse(text=""), spec)


def test_validate_response_requires_error_fields():
    spec = ProviderSpec(id="p1", type="stub")
    with pytest.raises(ContractViolationError):
        validate_response(ProviderResponse(error={"code": "X"}), spec)


class EmptyAdapter(ProviderAdapter):
    def run(self, context: ProviderRunContext) -> ProviderResponse:
        return ProviderResponse(text="")


def test_contract_violation_becomes_error_run(tmp_path):
    job = _make_job(tmp_path)
    config = Config(
        templates_glob="",
        payloads_glob="",
        output_root=str(tmp_path / "runs"),
        providers=[ProviderSpec(id="stub", type="stub")],
    )

    result = executor._run_single_job(job, config, {"stub": EmptyAdapter(ProviderSpec(id="stub", type="stub"))})

    assert result.status == RunStatus.ERROR
    assert result.error["code"] == "CONTRACT_VIOLATION"
    run_json = json.loads(pathlib.Path(result.artifact_paths.run_json).read_text())
    provider_meta = run_json["provider"]["meta"]
    assert provider_meta["provider_id"] == "stub"
    assert provider_meta["provider_type"] == "stub"


--- tests/test_core.py ---
import pytest
from dataclasses import asdict
from promptbench.core.types import ProviderSpec, Config, RunStatus
from promptbench.core.errors import PromptbenchError, ProviderError, to_error_dict

def test_provider_spec_serialization():
    spec = ProviderSpec(id="test", type="lmstudio", model="gpt-4")
    data = asdict(spec)
    assert data["id"] == "test"
    assert data["type"] == "lmstudio"
    assert data["model"] == "gpt-4"
    assert data["args"] == []
    assert data["env"] == {}

def test_config_defaults():
    spec = ProviderSpec(id="p1", type="lmstudio")
    config = Config(
        templates_glob="templates/*.md",
        payloads_glob="payloads/*.md",
        output_root="runs",
        providers=[spec]
    )
    assert config.concurrency == 1
    assert config.retries == 0
    assert config.use_eventing_executor is False
    assert config.skills_glob is None
    assert config.write_markdown_output is False
    assert config.skill_router.enabled is False

def test_error_serialization():
    err = ProviderError("connection failed", details={"host": "localhost"})
    data = err.to_dict()
    assert data["code"] == "PROVIDER_ERROR"
    assert data["message"] == "connection failed"
    assert data["details"] == {"host": "localhost"}

def test_to_error_dict_wrapper():
    raw_err = ValueError("bad value")
    data = to_error_dict(raw_err)
    assert data["code"] == "UNKNOWN_ERROR"
    assert data["message"] == "bad value"
    assert data["details"]["type"] == "ValueError"

    pb_err = PromptbenchError("pb error", code="CUSTOM")
    data = to_error_dict(pb_err)
    assert data["code"] == "CUSTOM"
    assert data["message"] == "pb error"


--- tests/test_engine_composition.py ---
import pytest
from promptbench.core.compose import compose_system
from promptbench.core.types import CompositionMode

def test_engine_composition_replaces_last_line():
    template = "Line 1\nOptimize this input:\n"
    payload = "Find the bug."
    # The last non-empty line is "Optimize this input:"
    result = compose_system(template, payload, mode=CompositionMode.REPLACE_LAST_LINE)
    assert result == "Line 1\nUser prompt: Find the bug.\n"

def test_engine_composition_with_trailing_blank_lines():
    template = "Instruction 1\nInstruction 2 (to be replaced)\n\n\n"
    payload = "Payload"
    result = compose_system(template, payload, mode=CompositionMode.REPLACE_LAST_LINE)
    # Replaces "Instruction 2", preserves three \n
    assert result == "Instruction 1\nUser prompt: Payload\n\n\n"

def test_engine_composition_empty_template():
    result = compose_system("", "Payload", mode=CompositionMode.REPLACE_LAST_LINE)
    assert result == "User prompt: Payload"

def test_engine_composition_only_whitespace_template():
    template = "   \n\n"
    result = compose_system(template, "Payload", mode=CompositionMode.REPLACE_LAST_LINE)
    assert result == "User prompt: Payload\n   \n\n"


--- tests/test_eventing_executor.py ---
import json
import pathlib
from io import StringIO

from promptbench.core.types import ProviderSpec, JobSpec, Config
from promptbench.providers.base import ProviderAdapter, ProviderRunContext
from promptbench.core.types import ProviderResponse
from promptbench.runner.eventing_executor import run_jobs_with_events


class StubAdapter(ProviderAdapter):
    def run(self, context: ProviderRunContext) -> ProviderResponse:
        return ProviderResponse(text="ok")


def test_run_jobs_with_events_emits_jsonl(tmp_path):
    templates = tmp_path / "templates"
    templates.mkdir()
    (templates / "t1.md").write_text("Hello {{USER_PROMPT}}")

    payloads = tmp_path / "payloads"
    payloads.mkdir()
    (payloads / "p1.md").write_text("World")

    output_root = tmp_path / "runs"

    spec = ProviderSpec(id="stub", type="stub")
    config = Config(
        templates_glob=str(templates / "*.md"),
        payloads_glob=str(payloads / "*.md"),
        output_root=str(output_root),
        providers=[spec],
    )

    jobs = [
        JobSpec(
            id="j1",
            template_path=str(templates / "t1.md"),
            payload_path=str(payloads / "p1.md"),
            provider_id="stub",
        )
    ]

    adapters = {"stub": StubAdapter(spec)}
    event_sink = StringIO()

    run_jobs_with_events(jobs, config, adapters, event_sink, run_id="run-evt")

    lines = [line for line in event_sink.getvalue().splitlines() if line]
    parsed = [json.loads(line) for line in lines]

    types = [item["type"] for item in parsed]
    assert "JOB_STARTED" in types
    assert "JOB_COMPLETED" in types
    assert "ARTIFACT_WRITTEN" in types

    job_ids = {item["job_id"] for item in parsed if item["type"] in ("JOB_STARTED", "JOB_COMPLETED")}
    assert job_ids == {"j1"}

    started = next(item for item in parsed if item["type"] == "JOB_STARTED")
    assert started["payload"]["attempt"] == 1

    completed = next(item for item in parsed if item["type"] == "JOB_COMPLETED")
    assert completed["payload"]["status"] == "ok"
    assert "duration_s" in completed["payload"]
    assert completed["payload"]["error_code"] is None
