diff --git a/AGENTS.md b/AGENTS.md index dcfaf64..91b617f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,6 +43,7 @@ This is a **Spec-Kit preset, extension, and bundle**, not an application. It def | `scripts/resolve-bug-context.py` | Records the standard bug extension's active bug directory in `feature.json` | | `scripts/check-bug-verdict.py` | Validates the standard bug extension's `test.md` result | | `scripts/init-quick.py` | Reads the run's `inputs.json`/`resolved-spec.txt` and creates or reuses a Quick Flow feature directory and `feature.json` pointer (`type: "quick"`) | +| `scripts/preserve-quick-review.py` | Commits Quick Flow work before a failed review's resolution gate | | `scripts/resolve-pr-template.py` | Discovers a pull-request template in the downstream project using GitHub-standard search paths | | `scripts/load-models.py` | Resolves the per-step model config (`model.config.json`) into `load-models.output.data` for each flow | | `scripts/package-preset.py` | Builds the preset ZIP package | @@ -121,7 +122,7 @@ Must preserve: - **Doc Reconciliation**: The post-implementation step that updates layered documentation and flags code-vs-docs conflicts. - **Spec-Kit Contract**: The interface between workflow steps where `stdout` of one step becomes the `args` of the next. - **Bugfix Flow**: A standard Spec-Kit bug workflow (`resolve → assess → fix → test → finish`) for surgical bugfixes without spec/plan/tasks generation. -- **Quick Flow**: A lightweight workflow (`resolve → init-quick → quick-plan → plan-gate → implement → review-fix → doc-check → finish`) for trivial, well-defined changes (label renames, message additions). Generates a minimal plan reviewed at a human gate, but no spec or task-list. Self-fixing review in a single pass. +- **Quick Flow**: A lightweight workflow (`resolve → init-quick → quick-plan → plan-gate → record-scope → implement → review-fix → resolution → doc-check → finish`) for trivial, well-defined changes (label renames, message additions). The reviewer validates the gate-approved plan scope, and a FAIL routes to explicit partial-ship, Feature Flow handoff, or abort choices. - **Sub-Agent Delegation**: The `sub-agent-delegation` preset's mechanism-neutral preamble, prepended to the parallelizable core commands. It maps the active integration (read from `.specify/integration.json`) to that backend's sub-agent primitive and falls back to sequential execution when none exists. ## Known Issues @@ -134,7 +135,7 @@ Must preserve: - **Agent behaviors**: See `commands/speckit.extendedflow.documentation.md`, `commands/speckit.extendedflow.finish.md`, `commands/speckit.extendedflow.quick-plan.md`, `commands/speckit.extendedflow.quick-implement.md`, `commands/speckit.extendedflow.quick-review.md`, `commands/speckit.extendedflow.doc-check.md`, plus the standard `speckit.bug.assess`, `speckit.bug.fix`, and `speckit.bug.test` commands from Spec-Kit's `bug` extension. The Feature Flow's QA loop uses the standard `speckit.analyze` and `speckit.implement`/`speckit.converge` commands. - **Output templates**: See `templates/review-findings.md`, `templates/documentation.md` - **Input resolution logic**: See `scripts/resolve-spec.py` -- **Branch creation and verdict extraction**: See `scripts/create-branch.py`, `scripts/check-converge.py`, `scripts/extract-verdict.py`, `scripts/resolve-bug-context.py`, `scripts/check-bug-verdict.py` +- **Branch creation and verdict extraction**: See `scripts/create-branch.py`, `scripts/check-converge.py`, `scripts/extract-verdict.py`, `scripts/preserve-quick-review.py`, `scripts/resolve-bug-context.py`, `scripts/check-bug-verdict.py` - **PR template resolution**: See `scripts/resolve-pr-template.py` - **Bundle composition**: See `bundle.yml` - **Architecture & design rationale**: See `README.md` diff --git a/README.md b/README.md index 6c00c53..728ef0b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Extended Flow ships a **family of composable flows** that share issue-to-PR auto - **Feature Flow** — the SDD lifecycle for new features (`specify → plan → tasks → analyze → implement → converge → documentation → finish`). - **Bugfix Flow** — the standard Spec-Kit bug lifecycle (`assess → fix → test → finish`), skipping spec/plan/tasks generation. -- **Quick Flow** — a lightweight pipeline for trivial, well-defined changes (`plan → plan-gate → implement → review-fix → doc-check → finish`). A minimal plan is reviewed at a human gate; no spec or task-list, and the self-fixing review runs in a single pass. +- **Quick Flow** — a lightweight pipeline for trivial, well-defined changes (`plan → plan-gate → implement → review-fix → resolution → doc-check → finish`). A minimal plan is approved at a human gate and becomes the review baseline; no spec or task-list, and the self-fixing review runs in a single pass. See [Flows](docs/flows.md) for the full diagrams and step-by-step breakdown. The family is designed to grow — future flows slot in as peers. @@ -110,7 +110,7 @@ specify workflow run spec-kit-quick-flow \ --input issue="42" ``` -**What happens:** The workflow writes a minimal plan from your instruction and pauses at a plan gate for your approval, then implements the change, runs a self-fixing review in a single pass, checks documentation impact, and commits and opens a PR. No spec or task-list generation — ideal for label changes, message additions, and other trivial tweaks. +**What happens:** The workflow writes a minimal plan from your instruction and pauses at a plan gate for your approval. The approved scope guides implementation and review, then the flow checks documentation impact and commits and opens a PR. A genuine review FAIL preserves a WIP commit and offers explicit partial-ship, Feature Flow handoff, or abort choices. No spec or task-list generation — ideal for label changes, message additions, and other trivial tweaks.
Other input modes @@ -203,7 +203,7 @@ A standard Spec-Kit bug lifecycle for surgical bugfixes. It uses `speckit.bug.as ### Quick Flow -A lightweight pipeline for trivial, well-defined changes. Produces a minimal plan that pauses at a human gate, then implements and uses a **self-fixing review** in a single pass. No spec or task-list. See [docs/flows.md](docs/flows.md#quick-flow) for the full diagram and step table. +A lightweight pipeline for trivial, well-defined changes. Produces a minimal plan that pauses at a human gate, then implements and uses a **self-fixing review** in a single pass. Its reviewer uses the approved plan as scope, so documented deferrals do not fail review. A genuine FAIL is resolved explicitly as partial ship, Feature Flow handoff, or abort. No spec or task-list. See [docs/flows.md](docs/flows.md#quick-flow) for the full diagram and step table. --- diff --git a/commands/speckit.extendedflow.doc-check.md b/commands/speckit.extendedflow.doc-check.md index 0b4480b..d092ecd 100644 --- a/commands/speckit.extendedflow.doc-check.md +++ b/commands/speckit.extendedflow.doc-check.md @@ -9,7 +9,7 @@ You are the documentation safety net for simple changes. Full documentation reco ## Inputs 1. **Feature Directory** — read `.specify/feature.json` to determine the current feature directory. -2. **Instruction** — read `specs//instruction.md` for context on what was changed. +2. **Approved Plan** — read `specs//plan.md` for context on what was changed, including an explicitly approved partial shipment. 3. **Code changes** — analyze the git diff to identify modified files. ## Steps diff --git a/commands/speckit.extendedflow.finish.md b/commands/speckit.extendedflow.finish.md index 108ea3d..19326d4 100644 --- a/commands/speckit.extendedflow.finish.md +++ b/commands/speckit.extendedflow.finish.md @@ -81,6 +81,7 @@ Read the following files from the feature directory (use `feature_directory` fro **For quick flows (`type` IS `"quick"`):** - `instruction.md` — Extract the original change instruction (1-2 sentences). +- `plan.md` — Extract the approved in-scope work plus any deferred work. - `doc-check.md` — Extract the documentation check summary (1 sentence). If any of these files do not exist, skip them silently. @@ -91,9 +92,11 @@ For feature flows, read `tasks.md` from the feature directory. The workflow has - If no unchecked tasks remain, note "Converged against spec, plan, and tasks". - If unchecked tasks remain, note "Implementation not fully converged". -For quick flows, find the latest (highest iteration number) `review-findings-*-PASS.md` file inside the feature directory. -- If found, extract the verdict (`PASS`) and the `## Summary` section (2-3 sentences). -- If no PASS review findings exist, note "Quick review not yet recorded". +For quick flows, find the latest (highest iteration number) `review-findings-*-{PASS|FAIL}.md` file inside the feature directory. +- If it is PASS, extract the verdict and the `## Summary` section (2-3 sentences). +- If it is FAIL, read `.specify/workflows/runs//state.json`. Continue only when `steps.quick-review-resolution.output.choice` is `ship-partial`; otherwise stop without cleanup, commit, or PR creation because the unresolved work must be escalated or aborted. +- For an approved partial shipment, state that the Quick Flow review reported unresolved in-scope work, the operator explicitly selected `ship-partial`, and summarize the deferred work from `plan.md`. +- If no review findings exist, stop without cleanup, commit, or PR creation. For bugfix flows (`type` IS `"bug"`), read `test.md` and extract its `Result` field (`verified`, `partial`, or `failed`) and summary. Do not look for a review-findings file. @@ -176,7 +179,7 @@ Use the template content as the starting point. Fill the following known section Known section headings (case-insensitive, match `# ` or `## ` prefixes): - `Summary` / `Description` / `Overview` → Fill with the spec summary (feature), root-cause + fix strategy (bugfix), or original instruction (quick). - `Changes` / `What Changed` / `What does this PR do?` → Fill with the main implementation changes (files/modules touched). -- `Testing` / `Test Plan` / `How to test` → Fill with the convergence status (feature), quick review verdict (PASS), or bug verification result, and a brief note on test coverage (e.g., "Full test suite passes; bug reproduction test was RED then GREEN"). +- `Testing` / `Test Plan` / `How to test` → Fill with the convergence status (feature), quick review verdict (PASS or explicitly approved partial shipment), or bug verification result, and a brief note on test coverage (e.g., "Full test suite passes; bug reproduction test was RED then GREEN"). - `Documentation` / `Docs` → Fill with the documentation change summary from Step 2d, or "No documentation changes required." - `Related Issues` / `Closes` / `Fixes` → Ensure `Closes #` appears here or elsewhere in the body. @@ -194,7 +197,7 @@ Generate a structured body with these sections: ## Testing - + ## Documentation ` diff --git a/commands/speckit.extendedflow.quick-implement.md b/commands/speckit.extendedflow.quick-implement.md index e267a92..eb295d8 100644 --- a/commands/speckit.extendedflow.quick-implement.md +++ b/commands/speckit.extendedflow.quick-implement.md @@ -10,7 +10,7 @@ You are the implementer for simple changes: label renames, message additions, sm 1. **Instruction text** — passed as args (from `resolve-spec.py` stdout). This is the complete change request. 2. **Feature Directory** — read `.specify/feature.json` to determine the current feature directory (e.g., `specs/42-quick-change-login-label`). -3. **Plan** — read `specs//plan.md` (produced by quick-plan and approved at the gate). It names the files to change and the verification approach. +3. **Approved Plan** — read `specs//plan.md`. The plan was approved at the preceding gate. ## Outputs @@ -21,7 +21,7 @@ You are the implementer for simple changes: label renames, message additions, sm 1. Read `.specify/feature.json` to determine the feature directory. 2. Write the instruction text to `specs//instruction.md`. This preserves the original request for the review agent. -3. Read `specs//plan.md` for the approved files-to-change and verification approach. +3. Read `specs//plan.md` and follow the plan approved at the preceding gate. 4. Analyze the codebase to locate the relevant code for the requested change. 5. Implement the change — make the minimal, targeted modification described in the instruction and plan. 6. Run existing tests to verify no regressions. @@ -29,7 +29,7 @@ You are the implementer for simple changes: label renames, message additions, sm ## Constraints -- **Follow the approved plan** — implement the change the plan describes. If the plan is wrong or incomplete, report this and stop rather than expanding scope. +- **Follow the approved plan** — implement only the plan's `In Scope` work. Do not implement `Deferred` or `Out of Scope` items. If the plan is wrong or incomplete, report this and stop rather than expanding scope. - **Minimal change** — implement ONLY what the instruction requests. No refactors, no improvements beyond the request. - **No scope expansion** — do not add features, fix unrelated bugs, or restructure code. - **Preserve existing behavior** — the change must not break any existing tests or functionality. diff --git a/commands/speckit.extendedflow.quick-plan.md b/commands/speckit.extendedflow.quick-plan.md index 6218dc2..b7d5786 100644 --- a/commands/speckit.extendedflow.quick-plan.md +++ b/commands/speckit.extendedflow.quick-plan.md @@ -4,7 +4,7 @@ You are the **Quick Plan Agent** — a focused planning agent for trivial, well- ## Your Role -You are the planning step for simple changes: label renames, message additions, small tweaks. The instruction you receive IS the specification — it is complete and unambiguous. You do NOT generate specs or task breakdowns, and you do NOT write code. You produce a minimal plan that names the files to touch, the exact change, and how it will be verified. +You are the planning step for simple changes: label renames, message additions, small tweaks. The instruction you receive IS the specification — it is complete and unambiguous. You do NOT generate specs or task breakdowns, and you do NOT write code. You produce a minimal plan that names the approved scope, files to touch, exact change, and verification. ## Inputs @@ -34,6 +34,9 @@ Write `specs//plan.md` with this structure: ## Summary +## In Scope + + ## Files to Change | File | Change | |------|--------| @@ -42,12 +45,19 @@ Write `specs//plan.md` with this structure: ## Verification +## Deferred + + +## Out of Scope + + ## Risks / Assumptions ``` ## Constraints +- **Complete scope record** — `In Scope`, `Deferred`, and `Out of Scope` are mandatory. Use `None` for an empty section; never omit one. - **Minimal plan** — describe ONLY what the instruction requests. No refactors, no improvements beyond the request. - **No scope expansion** — do not plan features, unrelated bug fixes, or restructuring. - **No code changes** — you plan; you do not implement. diff --git a/commands/speckit.extendedflow.quick-review.md b/commands/speckit.extendedflow.quick-review.md index 93488e1..88dce43 100644 --- a/commands/speckit.extendedflow.quick-review.md +++ b/commands/speckit.extendedflow.quick-review.md @@ -1,6 +1,6 @@ # Spec-Kit Extended Flow Quick Review Agent -You are the **Quick Review Agent** — a self-correcting quality assurance agent for trivial changes. You review the implementation against the original instruction and **fix any issues you find directly**, in a single pass. +You are the **Quick Review Agent** — a self-correcting quality assurance agent for trivial changes. You review the implementation against the approved scope and **fix any issues you find directly**, in a single pass. ## Your Role @@ -10,25 +10,27 @@ You are both reviewer and fixer, combined into one step. For trivial changes (la 1. **Feature Directory** — read `.specify/feature.json` to determine the current feature directory. 2. **Instruction** — read `specs//instruction.md` for the original change request. -3. **Implementation** — analyze all code changes made by the quick-implement agent. +3. **Approved Plan** — read `plan.md`, which was approved at the preceding gate. +4. **Implementation** — analyze all code changes made by the quick-implement agent. ## Steps 1. Read `.specify/feature.json` to determine the feature directory. -2. Read `specs//instruction.md` for the authoritative change request. -3. Analyze the code changes against the instruction: - - Does the implementation satisfy the instruction completely? +2. Read `instruction.md` and `plan.md`. +3. Treat `plan.md`'s `In Scope` section and verification requirements as the review baseline. `Deferred` and `Out of Scope` sections are context, not failures. +4. Analyze the code changes against the approved scope: + - Does the implementation satisfy all in-scope outcomes? - Are there logic errors, typos, or missed locations? - Are existing tests still passing? - Are there any regressions? -4. **If you find issues — fix them directly.** Make surgical corrections in the same pass. -5. After fixing, re-verify: run tests, lints, and type-checks to confirm everything is clean. -6. Write `review-findings-1-{VERDICT}.md` inside the feature directory using the `review-findings` template. +5. **If you find issues — fix them directly.** Make surgical corrections in the same pass. +6. After fixing, re-verify: run tests, lints, and type-checks to confirm everything is clean. +7. Write `review-findings-1-{VERDICT}.md` inside the feature directory using the `review-findings` template. ## Verdict Rules -- **PASS** — The implementation (after your self-corrections, if any) satisfies the instruction completely. All tests pass, no regressions. -- **FAIL** — You found issues that you could NOT resolve yourself. This means the change is too complex for the Quick Flow and belongs in the Feature Flow. +- **PASS** — The implementation (after your self-corrections, if any) satisfies every approved in-scope outcome. Explicitly deferred or out-of-scope items do not cause FAIL. All applicable tests pass and there are no regressions. +- **FAIL** — You found an unresolved in-scope issue, could not verify the approved scope, or could not complete required verification. This may require Feature Flow, partial shipment, or an explicit abort decision. ## Output Requirements @@ -46,7 +48,8 @@ Specifically: 3. **Document findings**: Log any issues you found and how you resolved them (or why you could not). -4. **On FAIL**: Explain clearly what could not be resolved and why the Feature Flow is needed. +4. **On FAIL**: Explain clearly what could not be resolved, whether a partial shipment is safe, and why Feature Flow may be needed. +5. **Always complete successfully after writing the file.** `FAIL` is workflow data, not a command failure. Never exit nonzero solely because the verdict is FAIL. ## Constraints diff --git a/docs/flows.md b/docs/flows.md index 1089075..7ca7473 100644 --- a/docs/flows.md +++ b/docs/flows.md @@ -85,7 +85,7 @@ When started from a GitHub issue, the Bugfix Flow creates a `fix/-` ## Quick Flow -A lightweight pipeline for trivial, well-defined changes. It skips spec and task-list generation; instead it produces a **minimal plan** that a human approves at a gate before implementation. It uses a **self-fixing review** in a single pass — the reviewer finds issues and fixes them in one step. +A lightweight pipeline for trivial, well-defined changes. It skips spec and task-list generation; instead it produces a **minimal plan** that a human approves at a gate before implementation. That approved plan is also the review baseline. It uses a **self-fixing review** in a single pass — the reviewer finds issues and fixes them in one step. ```mermaid flowchart TD @@ -100,7 +100,11 @@ flowchart TD G --> I[quick-review] I --> J{PASS?} J -->|yes| K[doc-check] - J -->|no| H + J -->|no| N[preserve WIP commit] + N --> O{review resolution} + O -->|ship-partial| K + O -->|escalate| P[Feature Flow handoff] + O -->|abort| H K --> L[finish] L --> M[✅ done] ``` @@ -109,9 +113,10 @@ flowchart TD 2. **Plan** — Produce a minimal plan from the instruction (no tasks.md) 3. **Plan Gate** — Human reviews `plan.md`; approve to implement, reject to stop 4. **Implement** — Direct implementation from the instruction and approved plan -5. **Review + Fix** — Self-fixing review in a single pass -6. **Doc Check** — Lightweight documentation impact check -7. **Finish** — Cleanup, commit, PR +5. **Review + Fix** — Self-fixing review against the approved plan in a single pass +6. **Review Resolution** — A FAIL first preserves a WIP commit, then offers partial ship, Feature Flow escalation, or abort +7. **Doc Check** — Lightweight documentation impact check +8. **Finish** — Cleanup, commit, PR | Step | Type | Description | |------|------|-------------| @@ -122,8 +127,10 @@ flowchart TD | `quick-plan` | command | Produces a minimal `plan.md` from the instruction | | `quick-plan-gate` | gate | Human approves or rejects the plan | | `quick-implement` | command | Implements the change from the instruction and approved plan | -| `quick-review` | command | Self-fixing review: reviews and corrects in one pass → PASS or FAIL | +| `quick-review` | command | Self-fixing review against the approved plan: reviews and corrects in one pass → PASS or FAIL | +| `preserve-quick-review` | shell | On FAIL, commits the current work before a human decision | +| `quick-review-resolution` | gate | On FAIL, choose partial ship, Feature Flow handoff, or abort | | `doc-check` | command | Lightweight documentation impact check, flags conflicts | | `finish` | command | Cleans up, commits (`chore:` prefix), opens PR when issue provided | -**When to use Quick Flow:** Label renames, message additions, small tweaks — changes that are unambiguous, localized, and need no design decisions. If the self-fixing review returns FAIL, the change is too complex for Quick Flow and belongs in the Feature Flow. +**When to use Quick Flow:** Label renames, message additions, small tweaks — changes that are unambiguous, localized, and need no design decisions. Explicitly deferred or out-of-scope plan items do not fail review. If an in-scope issue remains after the self-fixing review, Quick Flow preserves the work and lets the operator ship the reviewed partial change, escalate the branch to Feature Flow, or abort. diff --git a/docs/reference.md b/docs/reference.md index 767dbba..6e685db 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -43,6 +43,7 @@ This split exists because Spec-Kit's architecture reserves commands for extensio | Resolve spec | `scripts/resolve-spec.py` | Resolves `spec`/`file`/`issue` inputs from the run directory and persists the result | | Create branch | `scripts/create-branch.py` | Creates feature branch from the run's issue | | Init quick | `scripts/init-quick.py` | Creates or reuses the Quick Flow feature directory and pointer | +| Preserve quick review | `scripts/preserve-quick-review.py` | Creates a WIP commit before a failed-review resolution choice | | Verify spec | `scripts/verify-spec.py` | Validates spec file was created | | Check converge | `scripts/check-converge.py` | Detects whether `speckit.converge` appended new tasks | | Extract verdict | `scripts/extract-verdict.py` | Extracts the Quick Flow PASS/FAIL from the review filename | diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 94f6e76..1b6b140 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -34,6 +34,10 @@ specify extension enable git **Quick Flow fails after an interrupted run (`Feature directory already exists`):** `init-quick` is idempotent as of v0.16.1. Re-running Quick Flow for the same issue and title reuses the existing feature directory, rewrites `.specify/feature.json`, and clears stale `instruction.md`, `plan.md`, `review-findings-*.md`, and `doc-check.md` from the interrupted run. If you still see this error, upgrade the installed preset and extension. See `scripts/init-quick.py`. +**Quick Flow review returns FAIL:** The review agent writes its verdict and completes successfully, allowing the workflow to record it deterministically. Before the resolution gate, the flow creates a WIP commit so no choice strands an uncommitted diff. Choose `ship-partial` only when the remaining work is explicitly acceptable; it continues through doc-check and finish. Choose `escalate` to keep the committed branch and artifacts for Feature Flow. Choose `abort` to stop cleanly after preserving the work. + +**Quick Flow review rejects an approved deferral:** Make sure the deferral is listed in the plan's `Deferred` or `Out of Scope` section. The reviewer uses the plan approved at the gate as its baseline, so those items do not count as missing implementation. + **Workflow not found by ID:** Install it: `specify workflow add .specify/presets/spec-kit-extended-flow/workflows/workflow.yml` **GitHub issue not resolving:** Ensure `gh` CLI is installed and authenticated (`gh auth status`). The `issue` parameter only supports issues from the current repository (bare number, e.g., `42`). Cross-repo references and full URLs are not supported. diff --git a/scripts/build-catalog.py b/scripts/build-catalog.py index fef2759..527bc4f 100755 --- a/scripts/build-catalog.py +++ b/scripts/build-catalog.py @@ -20,6 +20,7 @@ "check-bug-verdict.py", "verify-spec.py", "init-quick.py", + "preserve-quick-review.py", "resolve-pr-template.py", "load-models.py", ) diff --git a/scripts/package-preset.py b/scripts/package-preset.py index 275adae..b6cbaff 100755 --- a/scripts/package-preset.py +++ b/scripts/package-preset.py @@ -18,6 +18,7 @@ "check-bug-verdict.py", "verify-spec.py", "init-quick.py", + "preserve-quick-review.py", "resolve-pr-template.py", "load-models.py", ) diff --git a/scripts/preserve-quick-review.py b/scripts/preserve-quick-review.py new file mode 100755 index 0000000..5b237c2 --- /dev/null +++ b/scripts/preserve-quick-review.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Commit Quick Flow work before a human resolves a failed review.""" + +import json +from pathlib import Path +import re +import shutil +import subprocess +import sys + + +RUNS_DIR = Path(".specify") / "workflows" / "runs" +RUN_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$") + + +def error(message: str) -> int: + print(f"ERROR: {message}", file=sys.stderr) + return 1 + + +def run(command: list[str]) -> subprocess.CompletedProcess[bytes]: + return subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=False) + + +def main() -> int: + run_id = sys.argv[1] if len(sys.argv) > 1 else "" + if not RUN_ID_PATTERN.fullmatch(run_id): + return error(f"Invalid run id: {run_id!r}.") + if shutil.which("git") is None: + return error("Git CLI (git) is required to preserve Quick Flow work.") + try: + pointer = json.loads(Path(".specify/feature.json").read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + return error("Could not read .specify/feature.json for the Quick Flow work.") + if not isinstance(pointer, dict) or pointer.get("type") != "quick": + return error("The active feature is not a Quick Flow feature.") + try: + inputs = json.loads((RUNS_DIR / run_id / "inputs.json").read_text(encoding="utf-8")) + except OSError: + return error(f"Workflow inputs not found: {RUNS_DIR / run_id / 'inputs.json'}") + except json.JSONDecodeError: + return error(f"Workflow inputs are not valid JSON: {RUNS_DIR / run_id / 'inputs.json'}") + values = inputs.get("inputs") if isinstance(inputs, dict) else None + issue = values.get("issue") if isinstance(values, dict) else "" + if not isinstance(issue, str): + return error("Workflow input 'issue' must be a string.") + + if run(["git", "add", "-A"]).returncode != 0: + return error("Failed to stage Quick Flow work.") + staged = run(["git", "diff", "--cached", "--quiet"]) + if staged.returncode == 0: + print("NO_CHANGES") + return 0 + if staged.returncode != 1: + return error("Failed to inspect staged Quick Flow work.") + message = "chore: preserve Quick Flow review failure" + if issue: + message += f" (#{issue})" + result = run(["git", "commit", "-m", message]) + if result.returncode != 0: + return error(result.stdout.decode(errors="surrogateescape").rstrip() or "Failed to preserve Quick Flow work.") + print("PRESERVED") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception as exc: + print(f"ERROR: Unexpected error: {exc}", file=sys.stderr) + raise SystemExit(1) diff --git a/templates/review-findings.md b/templates/review-findings.md index 4fc74fe..e1e811c 100644 --- a/templates/review-findings.md +++ b/templates/review-findings.md @@ -12,7 +12,7 @@ The verdict is encoded in the filename for machine consumption. |------------------|----------------------| | **Date** | {{date}} | | **Iteration** | {{iteration}} | -| **Spec Ref** | {{spec_reference}} | +| **Scope Ref** | {{spec_reference}} | | **Reviewer** | Spec-Kit Extended Flow Reviewer | --- @@ -50,7 +50,7 @@ The verdict is encoded in the filename for machine consumption. **Description**: {{detailed_description}} -**Expected Behavior** (per spec): +**Expected Behavior** (per approved scope): {{expected}} **Actual Behavior**: diff --git a/tests/test_maintenance.py b/tests/test_maintenance.py index 5bfd990..ea10c6a 100644 --- a/tests/test_maintenance.py +++ b/tests/test_maintenance.py @@ -24,6 +24,7 @@ "check-bug-verdict.py", "verify-spec.py", "init-quick.py", + "preserve-quick-review.py", "resolve-pr-template.py", "load-models.py", } diff --git a/tests/test_runtime_scripts.py b/tests/test_runtime_scripts.py index d224e87..2e6582a 100644 --- a/tests/test_runtime_scripts.py +++ b/tests/test_runtime_scripts.py @@ -389,6 +389,47 @@ def test_init_quick_uses_the_issue_title_when_an_issue_is_present(self): self.assertEqual((result.returncode, result.stdout, result.stderr), (0, b"specs/42-quick-add-user-auth\n", b"")) self.assertEqual(pointer, {"feature_directory": "specs/42-quick-add-user-auth", "type": "quick"}) + def test_preserve_quick_review_commits_staged_work_and_handles_no_changes(self): + with tempfile.TemporaryDirectory() as temporary_directory: + root = Path(temporary_directory) + bin_directory = root / "bin" + bin_directory.mkdir() + calls = root / "git-calls.txt" + self.write_command( + bin_directory, + "git", + """ + import pathlib + import sys + calls = pathlib.Path("git-calls.txt") + calls.write_text(calls.read_text() + " ".join(sys.argv[1:]) + "\\n" if calls.exists() else " ".join(sys.argv[1:]) + "\\n") + if sys.argv[1:4] == ["diff", "--cached", "--quiet"]: + raise SystemExit(1) + """, + ) + self.write_run_inputs(root, issue="42") + (root / ".specify/feature.json").write_text( + '{"feature_directory": "specs/quick-test", "type": "quick"}\n', encoding="utf-8" + ) + env = {"PATH": f"{bin_directory}:{os.environ['PATH']}"} + committed = self.run_script("preserve-quick-review.py", "run-1", cwd=root, env=env) + call_log = calls.read_text(encoding="utf-8") + + self.write_command( + bin_directory, + "git", + """ + import sys + if sys.argv[1:4] == ["diff", "--cached", "--quiet"]: + raise SystemExit(0) + """, + ) + unchanged = self.run_script("preserve-quick-review.py", "run-1", cwd=root, env=env) + + self.assertEqual((committed.returncode, committed.stdout, committed.stderr), (0, b"PRESERVED\n", b"")) + self.assertIn("commit -m chore: preserve Quick Flow review failure (#42)", call_log) + self.assertEqual((unchanged.returncode, unchanged.stdout, unchanged.stderr), (0, b"NO_CHANGES\n", b"")) + def test_verify_spec_accepts_alternate_feature_pointer_keys(self): with tempfile.TemporaryDirectory() as temporary_directory: root = Path(temporary_directory) @@ -592,4 +633,3 @@ def test_invalid_run_id_is_a_stderr_error(self): self.assertEqual(result.returncode, 1) self.assertEqual(result.stdout, b"") self.assertEqual(result.stderr, b"ERROR: Invalid run id: 'run 1'.\n") - diff --git a/tests/test_static_contracts.py b/tests/test_static_contracts.py index 920da39..37c1396 100644 --- a/tests/test_static_contracts.py +++ b/tests/test_static_contracts.py @@ -16,6 +16,7 @@ "check-bug-verdict.py", "verify-spec.py", "init-quick.py", + "preserve-quick-review.py", "resolve-pr-template.py", "load-models.py", ) @@ -84,6 +85,30 @@ def test_quick_flow_plans_and_gates_before_implementing(self): self.assertLess(gate_index, implement_index) self.assertIn("on_reject: abort", content) + def test_quick_flow_uses_the_approved_plan_and_routes_review_failures_to_a_gate(self): + workflow = (ROOT / "workflows" / "quick-flow.yml").read_text(encoding="utf-8") + review = (ROOT / "commands" / "speckit.extendedflow.quick-review.md").read_text(encoding="utf-8") + plan = (ROOT / "commands" / "speckit.extendedflow.quick-plan.md").read_text(encoding="utf-8") + + self.assertNotIn("record-quick-scope.py", workflow) + self.assertIn("preserve-quick-review.py", workflow) + self.assertIn("id: quick-review-resolution", workflow) + for option in ("ship-partial", "escalate", "abort"): + with self.subTest(option=option): + self.assertIn(option, workflow) + self.assertNotIn("stop-on-review-fail", workflow) + self.assertNotIn("review-failed", workflow) + self.assertIn("approved plan", review.lower()) + self.assertNotIn("approved-scope.json", review) + self.assertNotIn("sha-256", review.lower()) + self.assertNotIn("record-quick-scope.py", plan) + self.assertIn("Deferred", review) + self.assertIn("Out of Scope", review) + self.assertIn("Never exit nonzero solely because the verdict is FAIL", review) + for heading in ("## In Scope", "## Deferred", "## Out of Scope"): + with self.subTest(heading=heading): + self.assertIn(heading, plan) + def test_shell_steps_never_interpolate_user_input_or_step_output(self): """Shell `run:` lines may only interpolate the engine-validated run id. diff --git a/workflows/quick-flow.yml b/workflows/quick-flow.yml index 50ea176..a5f8302 100644 --- a/workflows/quick-flow.yml +++ b/workflows/quick-flow.yml @@ -5,7 +5,7 @@ workflow: name: "Spec-Kit Extended Flow — Quick Flow" version: "0.17.0" author: "Markus Wondrak" - description: "The Quick Flow: lightweight pipeline for trivial, well-defined changes (label renames, message additions, small tweaks). Produces a minimal plan (reviewed at a human gate), implements directly from the instruction, runs a self-fixing review in a single pass, checks documentation impact, and ships a PR. No spec or task-list generation." + description: "The Quick Flow: lightweight pipeline for trivial, well-defined changes (label renames, message additions, small tweaks). Produces a minimal plan reviewed at a human gate, implements its approved scope, runs a self-fixing review in a single pass, then ships, escalates, or aborts explicitly. No spec or task-list generation." requires: speckit_version: ">=0.8.5" @@ -94,22 +94,44 @@ steps: type: shell run: python3 .specify/presets/spec-kit-extended-flow/scripts/extract-verdict.py "{{ context.run_id }}" - - id: stop-on-review-fail + - id: route-quick-review type: if condition: "{{ steps.quick-review-verdict.output.stdout | contains('FAIL') }}" then: - - id: review-failed + - id: preserve-quick-review type: shell - run: "echo 'Quick review could not self-correct all issues. This change is too complex for the Quick Flow — use the Feature Flow instead. See review-findings-1-FAIL.md inside the current feature directory for details.' >&2; exit 1" - - - id: doc-check - command: speckit.extendedflow.doc-check - integration: "{{ inputs.integration }}" - model: "{{ steps.load-models.output.data.doc-check.model }}" - - - id: finish - command: speckit.extendedflow.finish - integration: "{{ inputs.integration }}" - model: "{{ steps.load-models.output.data.finish.model }}" - input: - args: "{{ context.run_id }}" + run: python3 .specify/presets/spec-kit-extended-flow/scripts/preserve-quick-review.py "{{ context.run_id }}" + - id: quick-review-resolution + type: gate + message: "Quick review found unresolved in-scope work. Choose ship-partial to continue with the committed partial change, escalate to preserve the branch for Feature Flow, or abort." + options: [ship-partial, escalate, abort] + on_reject: abort + - id: ship-partial + type: if + condition: "{{ steps.quick-review-resolution.output.choice == 'ship-partial' }}" + then: + - id: doc-check-partial + command: speckit.extendedflow.doc-check + integration: "{{ inputs.integration }}" + model: "{{ steps.load-models.output.data.doc-check-partial.model }}" + - id: finish-partial + command: speckit.extendedflow.finish + integration: "{{ inputs.integration }}" + model: "{{ steps.load-models.output.data.finish-partial.model }}" + input: + args: "{{ context.run_id }}" + else: + - id: feature-flow-handoff + type: shell + run: "echo 'Quick Flow work was preserved in a WIP commit. Continue this branch with the Feature Flow; review-findings-1-FAIL.md and the approved plan remain available until that flow finishes.'" + else: + - id: doc-check + command: speckit.extendedflow.doc-check + integration: "{{ inputs.integration }}" + model: "{{ steps.load-models.output.data.doc-check.model }}" + - id: finish + command: speckit.extendedflow.finish + integration: "{{ inputs.integration }}" + model: "{{ steps.load-models.output.data.finish.model }}" + input: + args: "{{ context.run_id }}"