From f8b6d46ad811374f865130712a837995f3212659 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Sat, 18 Jul 2026 22:47:20 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ af3a40320dd2 --- CONTRIBUTING.md | 2 +- README.md | 8 +- UPSTREAM.json | 35 ++-- boatstack/SKILL.md | 8 +- boatstack/export.go | 6 +- boatstack/export_test.go | 10 +- boatstack/next.go | 162 +++++++++++++++--- boatstack/next_test.go | 107 +++++++++++- boatstack/plan.go | 52 +++--- boatstack/references/workflow.md | 10 +- docs/evidence-engineered-coding.md | 4 +- docs/public-claims.json | 24 +-- docs/troubleshooting.md | 12 ++ labs/diagram-json/plan.lock.json | 2 +- .../2026-07-18-startup-recovery-routing.md | 3 + 15 files changed, 347 insertions(+), 98 deletions(-) create mode 100644 release-notes/2026-07-18-startup-recovery-routing.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2efd8e4..61b44f6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ # Contributing -Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/ae167a3ce7759793d4dfbd710eea7ecbbf577058/labs/12-product-engineering-loop). +Boatstack is a generated content distribution. Propose changes to workflow semantics, templates, evidence rules, or generated presentation in [Intelligence Flow](https://github.com/operatorstack/intelligence-flow/tree/af3a40320dd2525fccc99e67c7ca26a43ec7ded8/labs/12-product-engineering-loop). The Boatstack repository receives product/runtime changes through a generated pull request. Review the PR's `UPSTREAM.json`, tests, adapter diff, and context-size change; do not hand-edit generated output on `main`. `.github/workflows` is the exception: it is Boatstack's executable control plane, excluded from scheduled projection and changed only through a separate manually reviewed Boatstack PR. diff --git a/README.md b/README.md index 89e29f3..e4a0f6c 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,9 @@ Install Boatstack in its own infrastructure PR and merge it before starting a fe That is all you need to learn up front. Boatstack shows one next action at a time through approval, building, tests, review, and PR preparation. -When you return after an interruption, run `/boatstack-next` in Claude Code or Cursor, or `$boatstack next` in Codex. Boatstack reports the repository-verified stage and one next action without changing state; if nothing remains active, it reports **Feature complete**. +When you return after an interruption, run `/boatstack-next` in Claude Code or Cursor, or `$boatstack next` in Codex. Boatstack reports the repository-verified stage and one next action without changing state. It distinguishes a feature that has not started from one that is complete. -`$boatstack run` in Codex or `/boatstack-run` in Claude Code and Cursor drives every verified slice through PR publication. It fetches `origin`, checks branch freshness, and pauses for `a`, product decisions, and `o` or `u`; it never merges or deploys. +`$boatstack run` in Codex or `/boatstack-run` in Claude Code and Cursor starts from one saved plan and continues through publication, pausing for approvals and product decisions. It checks branch freshness before delivery; it never merges or deploys. In Claude Code and Cursor, that guidance moves through `/plan-gate` → `/build` → `/test-gate` → `/review-gate` → `/ship-gate`. In Codex, use the same operation names after `$boatstack`. @@ -66,6 +66,8 @@ In Claude Code and Cursor, that guidance moves through `/plan-gate` → `/build` After Build, describe changes normally. Boatstack records them, preserves valid work, and resumes at the earliest boundary. +Invoke `/repair` in Claude Code or Cursor, or `$boatstack repair` in Codex. It needs an active delivery and the exact change; earlier work routes to planning or Build. + ```text “This is wrong” → record → repair → test → review ↘ changed intent → approve delta @@ -97,7 +99,7 @@ You remain free to build however the work requires. Boatstack governs claims of | **Skill** — React guidance, gstack, Spec Kit, or another specialty | Adds expertise for a particular kind of work | | **Boatstack** | Carries the delivery path, saved context, and proof of completion across them | -Boatstack does not replace the agent, model, or skills. It is the repository-local delivery harness that keeps their work connected to the product decision and the standard for calling it complete. +Boatstack is a repository-local delivery harness. > **Designed for model flexibility · Quality uplift evaluation in progress** diff --git a/UPSTREAM.json b/UPSTREAM.json index 52bc589..2eeabcb 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -1,7 +1,7 @@ { "canonical_context": { - "characters": 41894, - "estimated_tokens": 10474, + "characters": 42600, + "estimated_tokens": 10650, "estimator": "ceil(total characters / 4); compactness signal, not provider billing", "files": [ "product-engineering-loop/references/workflow.md", @@ -12,12 +12,12 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "fb54f9f378de5d12f0f2f4fdbd8136cfd7c89142966884d26da448973f738eae", - "README.md": "4526095079a7063ca6f4c7db367447c11be607bf86348ba7c5b94f080b7724ec", + "CONTRIBUTING.md": "acc6358f197620db553f49454b13ca97230d3cf39f394a775524feeafde1c79c", + "README.md": "73147594dce1b867d0258c87301032ee5e20ad83ee74843fd076a31838880f0e", "assets/boatstack-journey.svg": "c1f7fe2741f5e9ca66bb3fe9b103e6364ba5acbca8b7a8054768ffd85cf325ea", "assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4", "assets/boatstack-portability.svg": "ce648f5581d16586d25824d3a8132ef1b3d88b73329179173120129d4f74fd24", - "boatstack/SKILL.md": "44657cc7a55359bfd0025eae501e31b829f9665005a02d24f3b6ccbef3db3bf0", + "boatstack/SKILL.md": "cee940821e2f972070126fc7e041655ff32695602013f1db041d2a125693b599", "boatstack/agents/openai.yaml": "68a30a60859556c5a26e16d184594ca243a6043d99c8cf7d66b5dd6d50a93cd1", "boatstack/assets/templates/adr.md": "c577a3c1c1319061f61deb053597e6e853657022185fe28b8f733327e2a78565", "boatstack/assets/templates/approval.md": "74b0b816703a6dce3c96c8f95f981af910b020b6908e7f76cf5630778637e9f5", @@ -34,8 +34,8 @@ "boatstack/cmd/boatstack-helper/main.go": "b4b8b43d80dbf60f15e18885ff8ee01637df6e47c0249938714d885163350539", "boatstack/delivery.go": "907a0ff8dc3e6120387eef3c7d97cdadb0b9dd8f788cb39e149aa7a6c4a6260a", "boatstack/delivery_test.go": "744d166757deafe5b9fc4f66b79b324de43dcc36a26a317572eaec73a8b21044", - "boatstack/export.go": "d8117e8c2da549a9a5e96794440f163053d82a4e945624a11f168133f430a761", - "boatstack/export_test.go": "d98aa90e24bb4dbe7947ab36243a36cb967889456186e5a775abd3d999c779e5", + "boatstack/export.go": "f097e3b143011c13c17fcba7cd147c19848b2f9b472862fe8cad4bbff6310e57", + "boatstack/export_test.go": "67c3b8ce09aa63bd8c5168f3d3fe56156c5b47cef4d2f83bbcaa7eb7302ad712", "boatstack/go.mod": "57c377eccea51372d6664de4169e2ca45806b046f7e8a98a1e35a9eb454b4b8d", "boatstack/hooks.go": "1d5d8c4bf7e6e867c8bf07e391d86158347269f856647a5d256f345bbb8d3c96", "boatstack/hooks_test.go": "c3f359416ea53f258d8747d0247381e8946efd4d4a5bcf072c4147f885475ad3", @@ -43,9 +43,9 @@ "boatstack/init_test.go": "46ef98d955224d7744a5d67798bbc001f2d4c2c6aa2ac8a802c56e91a3ad8fb8", "boatstack/init_transaction.go": "112456c4e1c4db54c4137bcf4f7a9a9e63399a6f5971e9b3dc952d0c4b2aa4b6", "boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c", - "boatstack/next.go": "3d3bad7aecc94f2507432393a0bee0e71d4e85a6017f858aafe684c5ec4bc84b", - "boatstack/next_test.go": "79ea79666065d8e100a86577bec00b427208cbf05fd6493cd2e0816bb54da38f", - "boatstack/plan.go": "6a8a6bf3352749516b65748c413c5d14d3499897570902aff707ba3de05bfc42", + "boatstack/next.go": "9a9b3b9dde0a528991d2b6ac358638b249a791a4eb3e7a2656bd43b652a2c628", + "boatstack/next_test.go": "1244fdf5e447646793ffdab81b8ff9403b849e9fa050a41bc9e7145bca08a0d2", + "boatstack/plan.go": "2497e1d32d88a8e433bc5996b57d175bda4e6ba334cf2510efb7c84fd88a98f3", "boatstack/plan_test.go": "006cdc6681f77e579c5a0f709e30ede759c337132d4f2f5193b7b79b29bd7149", "boatstack/planning.go": "3a26417a295e5dfc2b6dcac702287c04b6053e7b74215858a4ea11cf9f9dadfe", "boatstack/planning_test.go": "6b156a64182ed76d4c3d392b4c5a26abe5d8b81cea27ee12ac7c4627c827e186", @@ -55,7 +55,7 @@ "boatstack/references/failure-moves.md": "1d35126348d0b681976e8819665e16fd745fd65eca271492603cb80aab75bf49", "boatstack/references/irreversible-operation-boundary.md": "2a695f2d7de95cfc8750f107bef9c86581712aa1f02e7233b69b850d8c2af42e", "boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae", - "boatstack/references/workflow.md": "9edba7fe6cac8a4f67120058ba3c567a86a5982c49c65758b00dcfbb6dacfb13", + "boatstack/references/workflow.md": "7e889d106aaab0a4acbb2c0af6a79b914a7599f84cb03eb26d1f28c33c4e3bea", "boatstack/release.go": "fa2ac926df89c90c5844e938a2e02d4b8dbbaefbf85bb7a1a89fc51690bea520", "boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690", "boatstack/run.go": "a9afb239c5e2cb80f96dab594f0dc94852f2f16625b1de05c5110ee85f61cfaa", @@ -73,14 +73,14 @@ "docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3", "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", - "docs/evidence-engineered-coding.md": "2935abc292b02c6842ea17ace5af9eecd5464ea1f24a17f5d594dd2a49a1c003", + "docs/evidence-engineered-coding.md": "bbf3f5d0cddccae591aee3b92083ba78df20451a7f920d3b94129cf3ab33c563", "docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c", "docs/getting-started.md": "61efc6bd618bd6674687d8efac9e1bc425fe5c10e72f87e267461dd2a830736e", - "docs/public-claims.json": "3311b27e29ef81aa25a1503e2407ea12331ba4c626936ea917b9545fba236830", + "docs/public-claims.json": "13a35ca66fd3ff382da2e181b6f14b8d28fdbc2621a6c322fd5a67bc79d9cdd8", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", - "docs/troubleshooting.md": "5233172deea20a962a3557e96f864113c3f6242a835e207e108817f8051ff237", + "docs/troubleshooting.md": "6b3dc443752e88d13ca4b279fa8024061cb9095cc6822f7b79fa93d74fe8717c", "docs/validation-and-evidence.md": "e7d91ad49c6adb44784ebe7d94feceb6abd445857f9a0716f0758bf6b55296c5", "docs/why-these-steps.md": "80957af13979070e8b2f2a8db78ce06d20d152bbc8ec41c3a8003f28393f6369", "install.ps1": "960b2b20b406bb2878a560e9ace53fe7226bc510be6ee8466ce4e608beb5625a", @@ -90,7 +90,7 @@ "labs/diagram-json/compiled/evidence.md": "1ba1c989ade070a8ef9a508fbd788d100d7292f2dbacbb2bce895468019f619d", "labs/diagram-json/compiled/tasks.json": "88f60851abf79d851e9fccc754ff3040034ae595306bc87d64784c19eb403e71", "labs/diagram-json/compiled/test-matrix.json": "424657ff505768e50fa113801fd8363364a18269d5297480907a993d44063a39", - "labs/diagram-json/plan.lock.json": "4bdb23a2800a9f826a1298dfb89f63a64ac7004aa15779edeb935075a9fcaaae", + "labs/diagram-json/plan.lock.json": "e48d49d74d6b3f337f3d9ef14243a6f9bea44bf55641f3dea459d04e61d02626", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -112,12 +112,13 @@ "release-notes/2026-07-18-next-stage.md": "42ac8e9e45303fe5609cdd3fde10ca69ed349b2d4948445ed91d0e49d5c769b0", "release-notes/2026-07-18-run-through-ship.md": "e69d314fe65265eb1f38c933340772d15bbb53b74c33d9d489b8a55849f545c7", "release-notes/2026-07-18-safety-sql-boundaries.md": "32011ca3d02a371e8f3f2899ffb34df3af0843d18d25e7db95fbca32c2dcf18c", - "release-notes/2026-07-18-stacked-bar-mark.md": "c4d5bd5fb89c280d7fba015384fd795fcb8c31ffe501078aa55a90cbcf66ba7b" + "release-notes/2026-07-18-stacked-bar-mark.md": "c4d5bd5fb89c280d7fba015384fd795fcb8c31ffe501078aa55a90cbcf66ba7b", + "release-notes/2026-07-18-startup-recovery-routing.md": "c305a1c2b8347e1bc6d7fcbd4a8e629438cdeb4bcf6f181a7d6fc154a88f4318" }, "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "ae167a3ce7759793d4dfbd710eea7ecbbf577058", + "commit": "af3a40320dd2525fccc99e67c7ca26a43ec7ded8", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/SKILL.md b/boatstack/SKILL.md index 895adba..8b102b1 100644 --- a/boatstack/SKILL.md +++ b/boatstack/SKILL.md @@ -29,11 +29,11 @@ For the full state machine, read [workflow.md](references/workflow.md). For arti ## Report what is next -Run the project-local helper's read-only `next-status --repo . --json` inspection. Repository artifacts, managed delivery state, and gate receipts are evidence; conversation, terminal, worktree, and process observations are context only. Never run the returned operation automatically. If nothing remains active in an initialized repository, report **Feature complete** with **No action required**. If state is ambiguous or stale, name the blocker instead of choosing by recency. +Run the project-local helper's read-only `next-status --repo . --json` inspection. Repository artifacts, managed delivery state, and gate receipts are evidence; conversation, terminal, worktree, and process observations are context only. Never run the returned operation automatically. Distinguish a Boatstack feature that is complete from one that has not started: `NOT_STARTED` and `SOURCE_PLAN_READY` point to `auto-plan`, while `FEATURE_COMPLETE` requires no action. If state is ambiguous, stale, or invalid, name the blocker instead of choosing by recency or clearing artifacts. ## Run through ship -For `$boatstack run`, `/boatstack-run`, or natural language such as “run Boatstack through ship,” first run the read-only `next-status --repo . --json`. Return **Feature complete** immediately when nothing remains active, and stop on unverified, ambiguous, or stale state. Before executing a mutating next operation, run `run-preflight --repo . --json`. It fetches `origin` and verifies the current named branch contains the fetched delivery base and is not behind or diverged from its upstream. A failed fetch, missing remote/base, stale base, upstream drift, or constrained branch mismatch blocks before product or workflow mutation. Never repair freshness by merging, rebasing, switching or creating a constrained branch, discarding changes, force-pushing, or broadening permissions. +For `$boatstack run`, `/boatstack-run`, or natural language such as “run Boatstack through ship,” first run the read-only `next-status --repo . --json`. When one saved source plan is ready, enter `auto-plan`; when no source plan exists, stop and ask the user to save the host Plan-mode file. Return **Feature complete** only for a verified completed feature, and stop on unverified, ambiguous, stale, or invalid state. Before the first delivery-stage operation (`build`, `repair`, `test-gate`, `review-gate`, or `ship-gate`), run `run-preflight --repo . --json`. Planning and approval do not require a remote fetch. The preflight fetches `origin` and verifies the current named branch contains the fetched delivery base and is not behind or diverged from its upstream. A failed fetch, missing remote/base, stale base, upstream drift, or constrained branch mismatch blocks before delivery mutation. Never repair freshness by merging, rebasing, switching or creating a constrained branch, discarding changes, force-pushing, or broadening permissions. After preflight, repeatedly run `next-status --repo . --json`, execute only its verified next operation using the canonical semantics below, verify the resulting repository state, and resolve again. Continue across all declared slices. Pause for explicit `a` plan approval, material product questions, and the exact `o` or `u` PR confirmation; a valid answer resumes the foreground run in the current host session. The run invocation itself is never approval or publication authority. Same-intent test/review failures may be recorded and repaired for at most three complete repair-and-gate cycles per active slice in one invocation. Stop on amendments, ambiguity, safety failures, stale evidence, unsupported recovery, branch mismatch, or an exhausted budget. Do not persist autopilot state or use conversation as workflow evidence. Completion means every slice PR is published for review, never merged or deployed. @@ -147,7 +147,9 @@ Do not branch the workflow on model brand, price, or a guessed capability tier. ## Repair from ordinary conversation -Before any product edit, read managed delivery status. If a delivery is active and the user reports a problem or requests a modification, use `repair` even when they do not name Boatstack or a slash command. Compare the exact request with the current lock, acceptance criteria, diff, evidence, and receipts. Classify it as `implementation_repair`, `verification_repair`, `review_repair`, `requirement_amendment`, or `needs_clarification`, then invoke `record-change` before editing. +Before any product edit or explicit `repair`, run `next-status`. Repair requires an active managed delivery and the user's exact requested change. If no Boatstack feature has started, explain that there is nothing to repair yet and point to `auto-plan`; if a draft or approved feature has not activated, route to its verified `plan-gate` or `build` operation. If a delivery is active and the user reports a problem or requests a modification, use `repair` even when they do not name Boatstack or a slash command. Compare the exact request with the current lock, acceptance criteria, diff, evidence, and receipts. Classify it as `implementation_repair`, `verification_repair`, `review_repair`, `requirement_amendment`, or `needs_clarification`, then invoke `record-change` before editing. A published feature is immutable; an exact correction request starts a linked Boatstack feature rather than rewriting completed evidence. + +If Cursor reports `MainThreadShellExec not initialized`, the host failed before Boatstack's hook process started. Keep the hook fail-closed and make **Developer: Reload Window** the primary recovery, then retry the operation. Recommend the verified installer only when Boatstack itself reports a missing, drifted, unsafe, or checksum-invalid helper/runtime. Same-intent repair resumes at the helper-reported stage and reuses the existing gates. A requirement amendment or ambiguous expected behavior blocks product edits and returns to a concise Plan Gate delta. Never edit `changes.md`, ignored delivery state, or receipts directly; those are emitted by controlled transitions. Conversation history is never workflow authority. diff --git a/boatstack/export.go b/boatstack/export.go index 5800d81..81cd99d 100644 --- a/boatstack/export.go +++ b/boatstack/export.go @@ -234,12 +234,12 @@ func BuildExportBundle(configPath string, config ProjectConfig, rawConfig []byte } operations := map[string]string{ - "boatstack-next": "Run the project-local helper next-status --repo . --json. This operation is strictly read-only: do not run the reported operation, edit artifacts, contact GitHub, or advance a gate. Translate the structured result into the canonical response contract. Show the verified feature and active slice when present. If observed_stage is FEATURE_COMPLETE, respond Feature complete and make No action required the one next action. If verification_status is BLOCKED, name the ambiguity and make resolving it the one action. Conversation, terminal, worktree, or process observations may be included as clearly labeled context only and must never override the repository-backed result. Otherwise make the returned next_operation the one next action.", - "boatstack-run": "First run the read-only next-status --repo . --json. If FEATURE_COMPLETE, respond Feature complete with No action required without requiring a remote or fetching. Stop on UNVERIFIED, BLOCKED, ambiguous, or stale state. Before executing any mutating next_operation, run the project-local helper run-preflight --repo . --json; it must fetch origin and verify branch freshness. Stop on a blocked preflight; never merge, rebase, force-push, discard changes, switch branches, or create a constrained delivery branch to repair freshness. Then execute exactly the verified next_operation using the canonical operation semantics, verify the resulting repository state, and resolve again. Continue across every declared delivery slice. Pause for the exact plan approval reply a, any material product decision, and the exact PR publication reply o or u; after a valid reply in the current host session, automatically continue the run. A run request never supplies approval or publication authority. For a same-intent test or review failure, use repair, record the observation, and retry from the returned stage, up to three complete automated repair-and-gate cycles for the active slice in this invocation. Stop immediately on an amendment, ambiguity, unsafe or destructive capability, stale evidence, branch mismatch, unsupported recovery, or exhausted repair budget. Do not use conversation as workflow evidence and do not create durable autopilot state. Report the feature, active slice, stages completed during this invocation, completion or pause reason, repair-cycle count, and exactly one next action. Ship means publishing every declared slice PR for review; never merge or deploy.", + "boatstack-next": "Run the project-local helper next-status --repo . --json. This operation is strictly read-only: do not run the reported operation, edit artifacts, contact GitHub, or advance a gate. Translate the structured result into the canonical response contract. Show the verified feature and active slice when present. Distinguish NOT_STARTED and SOURCE_PLAN_READY, whose next operation is auto-plan, from FEATURE_COMPLETE, which responds Feature complete and requires no action. If verification_status is BLOCKED, name the ambiguity or invalid evidence and make its safe restoration the one action; never clear artifacts. Conversation, terminal, worktree, or process observations may be included as clearly labeled context only and must never override the repository-backed result. Otherwise make the returned next_operation the one next action.", + "boatstack-run": "First run the read-only next-status --repo . --json. If SOURCE_PLAN_READY, execute auto-plan without Git preflight and pause at its normal decision or approval boundary. If NOT_STARTED, respond Start a Boatstack feature and ask the user to save exactly one host Plan-mode file, then run /auto-plan; do not fetch or require a feature branch. If FEATURE_COMPLETE, respond Feature complete with No action required without requiring a remote or fetching. Stop on UNVERIFIED, BLOCKED, ambiguous, stale, or invalid state. Before executing the first delivery-stage next_operation (build, repair, test-gate, review-gate, or ship-gate), run the project-local helper run-preflight --repo . --json; planning and plan-gate do not require it. Stop on a blocked preflight; never merge, rebase, force-push, discard changes, switch branches, or create a constrained delivery branch to repair freshness. Then execute exactly the verified next_operation using the canonical operation semantics, verify the resulting repository state, and resolve again. Continue across every declared delivery slice. Pause for the exact plan approval reply a, any material product decision, and the exact PR publication reply o or u; after a valid reply in the current host session, automatically continue the run. A run request never supplies approval or publication authority. For a same-intent test or review failure, use repair, record the observation, and retry from the returned stage, up to three complete automated repair-and-gate cycles for the active slice in this invocation. Stop immediately on an amendment, ambiguity, unsafe or destructive capability, stale evidence, branch mismatch, unsupported recovery, or exhausted repair budget. If Cursor reports MainThreadShellExec not initialized, explain that Cursor failed before the Boatstack hook started and make Developer: Reload Window the one recovery action; do not recommend reinstall unless Boatstack reports a missing, drifted, unsafe, or checksum-invalid runtime. Do not use conversation as workflow evidence and do not create durable autopilot state. Report the feature, active slice, stages completed during this invocation, completion or pause reason, repair-cycle count, and exactly one next action. Ship means publishing every declared slice PR for review; never merge or deploy.", "auto-plan": "Discover exactly one saved Plan-mode file and refine it into a Markdown-only draft feature package whose canonical structured artifact is plan.md. Run check-plan read-only. Record affected_paths and structured side_effects for external writes; use an immutable target identity, transactional or fix-forward recovery, and destructive=false. Keep internal phases as tasks in one delivery slice. Only when the accepted outcome explicitly needs multiple PRs, declare ordered delivery_slices and assign every task exactly once; plan approval never authorizes publication. Do not implement, create JSON or locks, or imply acceptance. If ready, respond with Plan ready and make Run /plan-gate the one next action. If decisions remain, respond with I need your input and ask only 1-3 material questions.", "plan-gate": "Run check-plan read-only, present its fingerprint and all open decisions, and require explicit human approval. While plan approval is pending, the normal user action is the exact standalone reply a. Trim surrounding whitespace and match a case-insensitively; do not treat [a] or an a embedded in other text as approval. Continue accepting the full reply approve for compatibility, but do not advertise it in the user-facing response. Resolve approved_by from an explicit supplied identity, otherwise from the authenticated GitHub login when available; ask one short identity follow-up only when neither exists, and never infer it from a filesystem username, commit history, or agent identity. On approval invoke record-approval with the resolved human, RFC3339 timestamp, and exact displayed fingerprint so it writes only approval.md. While pending, respond Ready for your approval and render the one next action as: Reply `a` to approve. After recording, respond Approved — ready to build and make entering the host execution mode and running /build the one next action. Remain in Plan mode; do not compile or request an early mode switch.", "build": "First confirm the host is in an execution-capable mode. If the mode transition is rejected or product-code writes remain unavailable, return READY_FOR_BUILD internally without activating the plan, compiling JSON, or writing a lock. Only then locate plan.md and approval.md and run activate-plan before the first product-code edit. Stop if it reports BLOCKED. Read delivery-status and implement only the active delivery slice task_ids. Run the internal repository safety check after operational or high-risk edits; a destructive capability blocks execution and gate progression but does not block reviewable source editing. Implementation tactics remain open inside the approved boundary, but push and PR mutation are never build tactics and are denied while managed delivery is active. On success respond Build complete and make Run /test-gate the one next action. When a new product decision blocks work, respond Build needs a decision and ask only that question.", - "repair": "Read delivery-status, the current plan lock and acceptance criteria, the actual diff, and current receipts. Compare the user's exact free-form request with approved intent. Classify it as implementation_repair, verification_repair, review_repair, requirement_amendment, or needs_clarification, then invoke record-change before any product edit. Same-intent repairs may proceed at the returned RESUME_STAGE; requirement amendments and ambiguous intent must stop for a concise plan amendment or one clarifying question. A published delivery is immutable: plan its correction under a new feature id with parent_delivery set to the published feature. Never edit changes.md or managed delivery state directly. After a repair, reuse the existing /test-gate and /review-gate; do not invent repair-specific gates.", + "repair": "First run next-status --repo . --json. Repair requires an active managed delivery and the user's exact free-form requested change. If NOT_STARTED or SOURCE_PLAN_READY, respond No active delivery to repair and make /auto-plan the one next action; do not ask for repair details. If DRAFT_PLAN or APPROVED, route to the returned plan-gate or build operation because no managed delivery exists yet. If FEATURE_COMPLETE and the user supplied an exact correction, preserve the published evidence and plan a linked Boatstack feature with parent_delivery set to the completed feature; otherwise ask for the exact correction. Stop on BLOCKED or INVALID_STATE and preserve all artifacts. For an active delivery, read delivery-status, the current plan lock and acceptance criteria, the actual diff, and current receipts. Compare the exact request with approved intent. Classify it as implementation_repair, verification_repair, review_repair, requirement_amendment, or needs_clarification, then invoke record-change before any product edit. Same-intent repairs may proceed at the returned RESUME_STAGE; requirement amendments and ambiguous intent must stop for a concise plan amendment or one clarifying question. Never edit changes.md or managed delivery state directly. After a repair, reuse the existing /test-gate and /review-gate; do not invent repair-specific gates. If Cursor reports MainThreadShellExec not initialized, make Developer: Reload Window the one recovery action because Boatstack's hook did not start; reserve reinstall guidance for Boatstack runtime integrity errors.", "test-gate": "Read delivery-status and test only the active delivery slice. Run the internal repository safety check, build a requirement-to-evidence matrix, and treat self-authored tests as evidence rather than the sole oracle. External writes require immutable target identity, transactional or fix-forward failure behavior, and an independent safety oracle. Commit the intentional slice product and evidence diff, then record-delivery-gate for the active feature and slice with --gate test and PASS or PASS_WITH_GAPS. Editing evidence Markdown alone never passes the gate. On pass respond Tests passed and make Run /review-gate the one next action. On failure respond Testing found a problem and make the required non-destructive repair the one next action.", "review-gate": "Read delivery-status and review the active slice's actual diff against approved intent, invariants, risks, gaps, and test evidence. Run the internal repository safety check. Executable destructive capability is blocking even when ordinary tests pass. On pass invoke record-delivery-gate for the same feature and slice with --gate review; it must reject a changed or untested diff. Then respond Review passed and make Run /ship-gate the one next action. When blocked respond Changes required and make the highest-priority blocking repair the one next action.", "ship-gate": "Prepare a reviewer-ready PR only; do not merge or deploy without separate authorization. Require the current managed feature approval, lock, test evidence, review evidence, and a passing repository safety scan, and commit the intentional product/artifact diff before projection. Internally run pr-context --repo . --feature in json and template formats, project the approved intent, actual committed diff, decisions, evidence, gaps, rollout, rollback, safety outcome, and operator-only recovery boundary into its required pr.md path, then run check-pr --repo . --preview . Always include why, what changed, review order, evidence, gaps/risks, rollout/rollback, and collapsed provenance; add UI evidence, security/privacy, migration, or operations sections only when the diff makes them relevant. Show the exact title and rendered body before any GitHub mutation. If PR_ACTION is open, respond PR ready and render the one next action as: Reply `o` to open PR. If update, render: Reply `u` to update PR. If manual, preserve the preview and give one manual publication action. Continue accepting the full replies open PR and update PR for compatibility without advertising them. Only after the matching state-scoped shortcut or compatible full reply: commit only the reviewed pr.md, rerun check-pr and require the same preview fingerprint (PREVIEW_FINGERPRINT), then run publish-pr with --action open or update and that fingerprint. The publisher performs a non-force push and rechecks context before GitHub mutation. If the diff or evidence changes, regenerate instead. If a required check fails on the base branch too, record the evidence and recommend a separate repair PR. Never edit unrelated code in this approved feature branch; a policy-approved bypass requires explicit human authorization. After publication respond PR opened with the link and make Review the PR the one next action; never imply merge authorization. If publish-pr returns UPDATE_AVAILABLE, keep Review the PR as the only next action and append a collapsed update notice saying no files changed and /boatstack-update may be run from the clean default branch after this feature PR merges. Do not check for releases before successful publication.", diff --git a/boatstack/export_test.go b/boatstack/export_test.go index 126b7b3..db11c82 100644 --- a/boatstack/export_test.go +++ b/boatstack/export_test.go @@ -169,7 +169,7 @@ func TestExportAndDriftCheck(t *testing.T) { planGate := string(bundle.Files[".cursor/commands/plan-gate.md"]) build := string(bundle.Files[".cursor/commands/build.md"]) responseOutcomes := map[string][]string{ - "boatstack-run": {"Feature complete"}, + "boatstack-run": {"Start a Boatstack feature", "Feature complete"}, "auto-plan": {"Plan ready", "I need your input"}, "plan-gate": {"Ready for your approval", "Approved — ready to build"}, "build": {"Build complete", "Build needs a decision"}, @@ -280,11 +280,17 @@ func TestExportAndDriftCheck(t *testing.T) { } } repair := string(bundle.Files[".cursor/commands/repair.md"]) - for _, expected := range []string{"record-change", "implementation_repair", "verification_repair", "requirement_amendment", "needs_clarification", "/test-gate", "/review-gate"} { + for _, expected := range []string{"next-status", "No active delivery to repair", "NOT_STARTED", "DRAFT_PLAN", "APPROVED", "record-change", "implementation_repair", "verification_repair", "requirement_amendment", "needs_clarification", "/test-gate", "/review-gate", "MainThreadShellExec not initialized", "Developer: Reload Window"} { if !strings.Contains(repair, expected) { t.Fatalf("repair adapter is missing %q", expected) } } + runCommand := string(bundle.Files[".cursor/commands/boatstack-run.md"]) + for _, expected := range []string{"SOURCE_PLAN_READY", "NOT_STARTED", "auto-plan", "planning and plan-gate do not require", "MainThreadShellExec not initialized", "Developer: Reload Window"} { + if !strings.Contains(runCommand, expected) { + t.Fatalf("run adapter is missing startup recovery rule %q", expected) + } + } for _, path := range []string{".claude/skills/boatstack/SKILL.md", ".agents/skills/boatstack/SKILL.md"} { router := string(bundle.Files[path]) if !strings.Contains(router, "automatically use repair") || !strings.Contains(router, "active managed delivery") { diff --git a/boatstack/next.go b/boatstack/next.go index f290d5e..b7bb87c 100644 --- a/boatstack/next.go +++ b/boatstack/next.go @@ -24,6 +24,14 @@ type NextStatus struct { BlockingAmbiguity []string `json:"blocking_ambiguity,omitempty"` } +func blockedNextStatus(stage, operation, reason string, ambiguity ...string) NextStatus { + return NextStatus{ + SchemaVersion: nextStatusSchemaVersion, VerificationStatus: "BLOCKED", + ObservedStage: stage, NextOperation: operation, Reason: reason, + BlockingAmbiguity: ambiguity, + } +} + func featurePlanCandidates(repo string) ([]string, error) { root := filepath.Join(repo, ".product-loop", "features") entries, err := os.ReadDir(root) @@ -35,7 +43,14 @@ func featurePlanCandidates(repo string) ([]string, error) { } features := []string{} for _, entry := range entries { - if entry.IsDir() && featureSlugPattern.MatchString(entry.Name()) && fileExists(filepath.Join(root, entry.Name(), "plan.md")) { + if !entry.IsDir() || !featureSlugPattern.MatchString(entry.Name()) || !fileExists(filepath.Join(root, entry.Name(), "plan.md")) { + continue + } + statePath, stateErr := deliveryStatePath(repo, entry.Name()) + if stateErr != nil { + return nil, stateErr + } + if !fileExists(statePath) { features = append(features, entry.Name()) } } @@ -43,6 +58,72 @@ func featurePlanCandidates(repo string) ([]string, error) { return features, nil } +func unclaimedSourcePlanCandidates(repo string) ([]string, error) { + candidates, err := sourcePlanCandidates(repo) + if err != nil { + return nil, err + } + claimed := map[string]bool{} + root := filepath.Join(repo, ".product-loop", "features") + entries, readErr := os.ReadDir(root) + if readErr != nil && !os.IsNotExist(readErr) { + return nil, readErr + } + for _, entry := range entries { + if !entry.IsDir() || !featureSlugPattern.MatchString(entry.Name()) { + continue + } + planPath := filepath.Join(root, entry.Name(), "plan.md") + sourcePath, sourceErr := SourcePlanForStructuredPlan(planPath) + if sourceErr != nil { + continue + } + absolute, absoluteErr := filepath.Abs(sourcePath) + if absoluteErr != nil { + return nil, absoluteErr + } + claimed[filepath.Clean(absolute)] = true + } + unclaimed := []string{} + for _, candidate := range candidates { + absolute := candidate + if !filepath.IsAbs(absolute) { + absolute = filepath.Join(repo, filepath.FromSlash(candidate)) + } + absolute, err = filepath.Abs(absolute) + if err != nil { + return nil, err + } + if !claimed[filepath.Clean(absolute)] { + unclaimed = append(unclaimed, candidate) + } + } + return unclaimed, nil +} + +func orphanedFeatureArtifacts(repo string) ([]string, error) { + root := filepath.Join(repo, ".product-loop", "features") + entries, err := os.ReadDir(root) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, err + } + orphans := []string{} + for _, entry := range entries { + if !entry.IsDir() || !featureSlugPattern.MatchString(entry.Name()) { + continue + } + directory := filepath.Join(root, entry.Name()) + if fileExists(filepath.Join(directory, "pr.md")) && !fileExists(filepath.Join(directory, "plan.lock.json")) { + orphans = append(orphans, entry.Name()) + } + } + sort.Strings(orphans) + return orphans, nil +} + func nextForDelivery(repo, feature string) (NextStatus, error) { state, err := CurrentDeliveryState(repo, feature) if err != nil { @@ -125,7 +206,7 @@ func ResolveNext(repoPath string) (NextStatus, error) { active, err := ActiveManagedDeliveries(repo) if err != nil { - return NextStatus{}, err + return blockedNextStatus("INVALID_STATE", "repair-state", "Boatstack found invalid managed delivery state. Preserve the artifacts and restore the missing or stale evidence before continuing: "+err.Error()), nil } if len(active) > 1 { base.VerificationStatus = "BLOCKED" @@ -136,26 +217,11 @@ func ResolveNext(repoPath string) (NextStatus, error) { return base, nil } if len(active) == 1 { - return nextForDelivery(repo, active[0]) - } - completed, err := completedManagedStates(repo) - if err != nil { - return NextStatus{}, err - } - if len(completed) > 0 { - base.VerificationStatus = "VERIFIED" - base.ObservedStage = "FEATURE_COMPLETE" - base.NextOperation = "none" - if len(completed) == 1 { - base.Feature = completed[0].Feature - if len(completed[0].Slices) > 0 { - base.ActiveSlice = completed[0].Slices[len(completed[0].Slices)-1].ID - } - base.Reason = fmt.Sprintf("All managed slices for feature %q are already published.", completed[0].Feature) - } else { - base.Reason = "All managed delivery states are already published." + status, deliveryErr := nextForDelivery(repo, active[0]) + if deliveryErr != nil { + return blockedNextStatus("INVALID_STATE", "repair-state", "Boatstack could not verify the active managed delivery. Preserve the artifacts and restore its evidence before continuing: "+deliveryErr.Error()), nil } - return base, nil + return status, nil } candidates, err := featurePlanCandidates(repo) @@ -187,10 +253,58 @@ func ResolveNext(repoPath string) (NextStatus, error) { return base, nil } + orphans, err := orphanedFeatureArtifacts(repo) + if err != nil { + return NextStatus{}, err + } + if len(orphans) > 0 { + return blockedNextStatus("INVALID_STATE", "repair-state", "Boatstack found a PR preview without the plan lock required to verify it. Preserve the artifacts and restore the feature evidence before continuing.", orphans...), nil + } + + sourcePlans, sourceErr := unclaimedSourcePlanCandidates(repo) + if sourceErr != nil { + return NextStatus{}, sourceErr + } + if len(sourcePlans) == 1 { + base.VerificationStatus = "VERIFIED" + base.ObservedStage = "SOURCE_PLAN_READY" + base.NextOperation = "auto-plan" + base.Reason = fmt.Sprintf("Saved Plan-mode file %q is ready to become a Boatstack feature.", sourcePlans[0]) + return base, nil + } + if len(sourcePlans) > 1 { + base.VerificationStatus = "BLOCKED" + base.ObservedStage = "AMBIGUOUS" + base.NextOperation = "resolve-ambiguity" + base.Reason = "Multiple unclaimed Plan-mode files are available; Boatstack will not choose by recency." + base.BlockingAmbiguity = sourcePlans + return base, nil + } + + completed, err := completedManagedStates(repo) + if err != nil { + return blockedNextStatus("INVALID_STATE", "repair-state", "Boatstack found invalid completed delivery state. Preserve the artifacts and restore its evidence before continuing: "+err.Error()), nil + } + if len(completed) > 0 { + base.VerificationStatus = "VERIFIED" + base.ObservedStage = "FEATURE_COMPLETE" + base.NextOperation = "none" + if len(completed) == 1 { + base.Feature = completed[0].Feature + if len(completed[0].Slices) > 0 { + base.ActiveSlice = completed[0].Slices[len(completed[0].Slices)-1].ID + } + base.Reason = fmt.Sprintf("All managed slices for feature %q are already published.", completed[0].Feature) + } else { + base.Reason = "All managed delivery states are already published." + } + return base, nil + } + base.VerificationStatus = "VERIFIED" - base.ObservedStage = "FEATURE_COMPLETE" - base.NextOperation = "none" - base.Reason = "No managed Boatstack plan or delivery remains active." + base.ObservedStage = "NOT_STARTED" + base.NextOperation = "auto-plan" + base.Reason = "No Boatstack feature has started and no saved Plan-mode file is available." return base, nil } diff --git a/boatstack/next_test.go b/boatstack/next_test.go index 12c5801..593d677 100644 --- a/boatstack/next_test.go +++ b/boatstack/next_test.go @@ -45,13 +45,31 @@ func writeNextDelivery(t *testing.T, repo, feature, status string, activeIndex i } } -func TestResolveNextReportsFeatureCompleteWhenNothingIsActive(t *testing.T) { +func TestResolveNextReportsNotStartedWhenNoFeatureExists(t *testing.T) { repo := nextTestRepo(t) status, err := ResolveNext(repo) if err != nil { t.Fatal(err) } - if status.VerificationStatus != "VERIFIED" || status.ObservedStage != "FEATURE_COMPLETE" || status.NextOperation != "none" { + if status.VerificationStatus != "VERIFIED" || status.ObservedStage != "NOT_STARTED" || status.NextOperation != "auto-plan" { + t.Fatalf("unexpected status: %+v", status) + } +} + +func TestResolveNextReportsSavedSourcePlan(t *testing.T) { + repo := nextTestRepo(t) + intake := filepath.Join(repo, ".product-loop", "intake") + if err := os.MkdirAll(intake, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(intake, "feature.md"), []byte("# Feature\n"), 0o644); err != nil { + t.Fatal(err) + } + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + if status.ObservedStage != "SOURCE_PLAN_READY" || status.NextOperation != "auto-plan" { t.Fatalf("unexpected status: %+v", status) } } @@ -111,6 +129,17 @@ func TestResolveNextDeliveryTransitions(t *testing.T) { func TestResolveNextReportsFeatureCompleteAfterPublication(t *testing.T) { repo := nextTestRepo(t) writeNextDelivery(t, repo, "recovery", "PUBLISHED", 1) + intake := filepath.Join(repo, ".product-loop", "intake") + if err := os.MkdirAll(intake, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(intake, "source-plan.md"), []byte("# Source plan\n"), 0o644); err != nil { + t.Fatal(err) + } + plan := validPlan() + plan["feature_id"] = "recovery" + plan["source_plan_path"] = "../../intake/source-plan.md" + writeMarkdownPlan(t, filepath.Join(repo, ".product-loop", "features", "recovery", "plan.md"), plan, true) status, err := ResolveNext(repo) if err != nil { t.Fatal(err) @@ -126,6 +155,25 @@ func TestResolveNextReportsFeatureCompleteAfterPublication(t *testing.T) { } } +func TestResolveNextPrefersNewDraftOverCompletedHistory(t *testing.T) { + repo := nextTestRepo(t) + writeNextDelivery(t, repo, "published", "PUBLISHED", 1) + directory := filepath.Join(repo, ".product-loop", "features", "new-feature") + if err := os.MkdirAll(directory, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(directory, "plan.md"), []byte("# Plan\n"), 0o644); err != nil { + t.Fatal(err) + } + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + if status.ObservedStage != "DRAFT_PLAN" || status.Feature != "new-feature" || status.NextOperation != "plan-gate" { + t.Fatalf("completed history masked newer work: %+v", status) + } +} + func TestResolveNextBlocksMultipleActiveFeaturesWithoutMutation(t *testing.T) { repo := nextTestRepo(t) writeNextDelivery(t, repo, "first", "BUILD", 0) @@ -150,14 +198,63 @@ func TestResolveNextBlocksMultipleActiveFeaturesWithoutMutation(t *testing.T) { } } -func TestResolveNextRejectsStaleManagedState(t *testing.T) { +func TestResolveNextBlocksStaleManagedState(t *testing.T) { repo := nextTestRepo(t) writeNextDelivery(t, repo, "recovery", "BUILD", 0) lockPath := filepath.Join(repo, ".product-loop", "features", "recovery", "plan.lock.json") if err := os.WriteFile(lockPath, []byte("changed\n"), 0o644); err != nil { t.Fatal(err) } - if _, err := ResolveNext(repo); err == nil { - t.Fatal("stale managed state was accepted") + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + if status.VerificationStatus != "BLOCKED" || status.ObservedStage != "INVALID_STATE" || status.NextOperation != "repair-state" { + t.Fatalf("stale managed state was accepted: %+v", status) + } +} + +func TestResolveNextBlocksMissingLockAndOrphanPreview(t *testing.T) { + for _, test := range []struct { + name string + withState bool + }{ + {name: "managed state missing lock", withState: true}, + {name: "orphan preview", withState: false}, + } { + t.Run(test.name, func(t *testing.T) { + repo := nextTestRepo(t) + directory := filepath.Join(repo, ".product-loop", "features", "orphan") + if test.withState { + writeNextDelivery(t, repo, "orphan", "BUILD", 0) + if err := os.Remove(filepath.Join(directory, "plan.lock.json")); err != nil { + t.Fatal(err) + } + } + if err := os.MkdirAll(directory, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(directory, "pr.md"), []byte("# Preview\n"), 0o644); err != nil { + t.Fatal(err) + } + before, err := os.ReadFile(filepath.Join(directory, "pr.md")) + if err != nil { + t.Fatal(err) + } + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + after, err := os.ReadFile(filepath.Join(directory, "pr.md")) + if err != nil { + t.Fatal(err) + } + if status.VerificationStatus != "BLOCKED" || status.ObservedStage != "INVALID_STATE" || status.NextOperation != "repair-state" { + t.Fatalf("unexpected invalid state: %+v", status) + } + if !reflect.DeepEqual(before, after) { + t.Fatal("invalid-state inspection modified the orphan preview") + } + }) } } diff --git a/boatstack/plan.go b/boatstack/plan.go index 19d62c1..189e6f5 100644 --- a/boatstack/plan.go +++ b/boatstack/plan.go @@ -185,27 +185,11 @@ func CheckSourcePlan(path string) error { return nil } -func DiscoverSourcePlan(repo, explicit string) (string, error) { +func sourcePlanCandidates(repo string) ([]string, error) { repoAbsolute, err := filepath.Abs(repo) if err != nil { - return "", err - } - if strings.TrimSpace(explicit) != "" { - candidate := explicit - if !filepath.IsAbs(candidate) { - candidate = filepath.Join(repoAbsolute, candidate) - } - candidate = filepath.Clean(candidate) - if err := CheckSourcePlan(candidate); err != nil { - return "", err - } - relative, err := filepath.Rel(repoAbsolute, candidate) - if err == nil && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) { - return filepath.ToSlash(relative), nil - } - return candidate, nil + return nil, err } - roots := []string{ ".product-loop/intake", ".cursor/plans", @@ -220,7 +204,7 @@ func DiscoverSourcePlan(repo, explicit string) (string, error) { if os.IsNotExist(err) { continue } - return "", err + return nil, err } err := filepath.WalkDir(absoluteRoot, func(path string, entry os.DirEntry, walkErr error) error { if walkErr != nil { @@ -243,10 +227,38 @@ func DiscoverSourcePlan(repo, explicit string) (string, error) { return nil }) if err != nil { - return "", err + return nil, err } } sort.Strings(candidates) + return candidates, nil +} + +func DiscoverSourcePlan(repo, explicit string) (string, error) { + repoAbsolute, err := filepath.Abs(repo) + if err != nil { + return "", err + } + if strings.TrimSpace(explicit) != "" { + candidate := explicit + if !filepath.IsAbs(candidate) { + candidate = filepath.Join(repoAbsolute, candidate) + } + candidate = filepath.Clean(candidate) + if err := CheckSourcePlan(candidate); err != nil { + return "", err + } + relative, err := filepath.Rel(repoAbsolute, candidate) + if err == nil && relative != ".." && !strings.HasPrefix(relative, ".."+string(filepath.Separator)) { + return filepath.ToSlash(relative), nil + } + return candidate, nil + } + + candidates, err := sourcePlanCandidates(repoAbsolute) + if err != nil { + return "", err + } if len(candidates) == 0 { return "", fmt.Errorf("no saved Plan-mode file found; save the current host plan under .product-loop/intake/ and run auto-plan again") } diff --git a/boatstack/references/workflow.md b/boatstack/references/workflow.md index d638e52..d0473ec 100644 --- a/boatstack/references/workflow.md +++ b/boatstack/references/workflow.md @@ -36,7 +36,7 @@ Every installed host routes supported shell and MCP events through Boatstack's i After an external-write failure, preserve state and use only read-only diagnosis. Do not escalate privileges, broaden the target, or invent a reset. Use a transactional retry only when retry safety is demonstrated; otherwise stop and fix forward. Destructive recovery is operator-only outside Boatstack. See `irreversible-operation-boundary.md` for the classified operations and evaluation status. -Hooks are defense in depth rather than a complete sandbox. Protected systems still require least-privilege credentials, scoped service roles, backups, and service-side destructive approval. A missing, drifted, or failing helper denies execution and requires reinstall or repair. +Hooks are defense in depth rather than a complete sandbox. Protected systems still require least-privilege credentials, scoped service roles, backups, and service-side destructive approval. A missing, drifted, or failing helper denies execution and requires reinstall or repair. Cursor's exact `MainThreadShellExec not initialized` error occurs before the Boatstack hook starts; preserve fail-closed behavior, reload the Cursor window, and retry before diagnosing the Boatstack installation. ## User-facing response contract @@ -65,12 +65,12 @@ Lead with a plain outcome, never a machine code such as `PASS`, `PLAN_APPROVED`, | State | Outcome -> one next action | |---|---| -| `next`, `/boatstack-next`, `$boatstack next` active / complete / ambiguous | **Next Boatstack stage** -> run the one repository-backed operation; **Feature complete** -> no action required; **Boatstack state needs attention** -> resolve the named ambiguity | -| `run`, `/boatstack-run`, `$boatstack run` complete / paused / blocked | **Feature ready for review** -> review the published PRs; **Boatstack run paused** -> provide the one required approval, confirmation, or product answer; **Boatstack run needs attention** -> resolve the named freshness, safety, state, or repair blocker | +| `next`, `/boatstack-next`, `$boatstack next` not started / active / complete / ambiguous | **Start a Boatstack feature** -> save a Plan-mode file or run `auto-plan`; **Next Boatstack stage** -> run the one repository-backed operation; **Feature complete** -> no action required; **Boatstack state needs attention** -> resolve the named ambiguity or invalid evidence | +| `run`, `/boatstack-run`, `$boatstack run` not started / complete / paused / blocked | **Start a Boatstack feature** -> save a Plan-mode file; **Feature ready for review** -> review the published PRs; **Boatstack run paused** -> provide the one required approval, confirmation, or product answer; **Boatstack run needs attention** -> resolve the named freshness, safety, state, or repair blocker | | `auto-plan` ready / needs answers | **Plan ready** -> run `/plan-gate`; **I need your input** -> answer with the displayed choice keys or `r` for all recommendations | | `plan-gate` pending / approved | **Ready for your approval** -> reply `a` to approve; **Approved — ready to build** -> enter execution mode and run `/build` | | `build` success / paused | **Build complete** -> run `/test-gate`; **Build needs a decision** -> answer the blocking question | -| `repair` same intent / amendment | **Repair recorded** -> perform the reported resume stage; **Plan amendment required** -> review the proposed intent delta | +| `repair`, `/repair`, `$boatstack repair` not started / pre-build / same intent / amendment | **No active delivery to repair** -> run `auto-plan` or the verified pre-build gate; **Repair recorded** -> perform the reported resume stage; **Plan amendment required** -> review the proposed intent delta | | `test-gate` pass / blocked | **Tests passed** -> run `/review-gate`; **Testing found a problem** -> perform or authorize the repair | | `review-gate` pass / blocked | **Review passed** -> run `/ship-gate`; **Changes required** -> address the blocking finding | | `ship-gate` preview / published | **PR ready** -> reply `o` to open or `u` to update the previewed PR; **PR opened** -> review the PR; never imply merge authorization | @@ -79,7 +79,7 @@ Lead with a plain outcome, never a machine code such as `PASS`, `PLAN_APPROVED`, ### Foreground run coordinator -`run` is an opt-in foreground coordinator over the existing operations, not a second state machine. It first resolves the read-only repository state, returns **Feature complete** without requiring a remote when no work remains, and stops on unverified or blocked state. Before any workflow or product mutation it runs the versioned Git preflight, which fetches `origin`, requires the fetched remote base, verifies that the current named branch contains that base, rejects a behind or diverged upstream, and enforces any active slice branch constraints. It never merges, rebases, switches or creates constrained branches, discards changes, force-pushes, merges a PR, or deploys. +`run` is an opt-in foreground coordinator over the existing operations, not a second state machine. It first resolves the read-only repository state, enters `auto-plan` when one saved source plan exists, asks for a saved Plan-mode file when none exists, returns **Feature complete** without requiring a remote only for completed work, and stops on unverified or blocked state. Before the first delivery-stage mutation it runs the versioned Git preflight, which fetches `origin`, requires the fetched remote base, verifies that the current named branch contains that base, rejects a behind or diverged upstream, and enforces any active slice branch constraints. Planning and approval remain local and do not require a remote. It never merges, rebases, switches or creates constrained branches, discards changes, force-pushes, merges a PR, or deploys. After preflight, resolve the repository-backed next operation, execute exactly that canonical operation, verify the resulting state, and resolve again through all declared delivery slices. Pause for `a`, a material product answer, and `o` or `u`; after the valid state-scoped reply, continue in the current host session. The invocation does not replace either human authorization. Automatically record and repair same-intent test or review failures for at most three complete repair-and-gate cycles per active slice per invocation. Stop immediately for requirement amendments, ambiguous or stale state, unsafe capability, unsupported recovery, branch mismatch, or exhausted repairs. Store no durable run/autopilot mode; re-invocation reconstructs progress from canonical repository state. diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 51d065f..b16ad3a 100644 --- a/docs/evidence-engineered-coding.md +++ b/docs/evidence-engineered-coding.md @@ -96,7 +96,7 @@ subject to acceptance criteria pass approval is current ``` -That is why context trimming is not automatically an optimization. If removing state increases rework or false acceptance, total cost rises. The canonical runtime references are approximately **10474 estimated tokens**, while host adapters point to one operation at a time. +That is why context trimming is not automatically an optimization. If removing state increases rework or false acceptance, total cost rises. The canonical runtime references are approximately **10650 estimated tokens**, while host adapters point to one operation at a time. ## Control appears at transitions @@ -146,6 +146,6 @@ Delivery and system improvement also remain separate. A failed task may suggest ## What is evidence-backed -The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`ae167a3ce7759793d4dfbd710eea7ecbbf577058`](https://github.com/operatorstack/intelligence-flow/tree/ae167a3ce7759793d4dfbd710eea7ecbbf577058/labs/12-product-engineering-loop). +The current moves were derived from the Intelligence Flow benchmark corpus and product-repository studies. The generated source commit is [`af3a40320dd2525fccc99e67c7ca26a43ec7ded8`](https://github.com/operatorstack/intelligence-flow/tree/af3a40320dd2525fccc99e67c7ca26a43ec7ded8/labs/12-product-engineering-loop). The evidence supports specific failure mechanisms and guardrails. It does not establish that Boatstack is optimal, that control-theory notation proves software quality, or that one workflow dominates every team. Those are evaluation questions, so the distribution preserves measurements, provenance, gaps, and negative results. diff --git a/docs/public-claims.json b/docs/public-claims.json index b62db03..40101f5 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "ae167a3ce7759793d4dfbd710eea7ecbbf577058", + "source_commit": "af3a40320dd2525fccc99e67c7ca26a43ec7ded8", "statuses": ["verified", "observed", "still_being_evaluated"], "claims": [ { @@ -12,7 +12,7 @@ "readable_evidence": "why-these-steps.md#portable-workflow-and-state", "implementation": ["../boatstack/export.go", "../boatstack/references/artifacts.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "human-decisions", @@ -23,7 +23,7 @@ "readable_evidence": "why-these-steps.md#human-decisions", "implementation": ["../boatstack/references/workflow.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "validation-provenance", @@ -34,7 +34,7 @@ "readable_evidence": "why-these-steps.md#validation-provenance", "implementation": ["validation-and-evidence.md", "../boatstack/plan.go"], "verification": ["../boatstack/plan_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "irreversible-operations", @@ -46,7 +46,7 @@ "readable_evidence": "why-these-steps.md#irreversible-operations", "implementation": ["safety.md", "../boatstack/safety.go", "../boatstack/hooks.go"], "verification": ["../boatstack/safety_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "reviewer-ready-pr", @@ -57,7 +57,7 @@ "readable_evidence": "why-these-steps.md#reviewer-ready-pr", "implementation": ["../boatstack/pr.go", "getting-started.md"], "verification": ["../boatstack/pr_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "phase-scoped-delivery", @@ -68,7 +68,7 @@ "readable_evidence": "why-these-steps.md#phase-scoped-delivery", "implementation": ["../boatstack/delivery.go", "../boatstack/safety.go", "../boatstack/hooks.go", "../boatstack/references/workflow.md"], "verification": ["../boatstack/delivery_test.go", "../boatstack/pr_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "model-neutral-contract", @@ -79,7 +79,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md", "../boatstack/references/workflow.md"], "verification": ["../boatstack/export_test.go", "../boatstack/planning_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "cross-model-failures", @@ -90,7 +90,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "lower-cost-outcomes", @@ -101,7 +101,7 @@ "readable_evidence": "why-these-steps.md#model-choice-and-budget", "implementation": ["research-and-design.md"], "verification": ["benchmark-corpus-audit.md", "benchmark-submission-audit.md"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "git-worktree-activation", @@ -112,7 +112,7 @@ "readable_evidence": "why-these-steps.md#git-worktree-activation", "implementation": ["../boatstack/runtime_cache.go", "../boatstack/hooks.go"], "verification": ["../boatstack/runtime_cache_test.go", "../boatstack/hooks_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" }, { "id": "visible-updates", @@ -123,7 +123,7 @@ "readable_evidence": "why-these-steps.md#visible-updates", "implementation": ["../boatstack/update.go", "../boatstack/init.go"], "verification": ["../boatstack/update_test.go", "../boatstack/init_test.go", "../boatstack/export_test.go"], - "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" + "last_verified_version": "source:af3a40320dd2525fccc99e67c7ca26a43ec7ded8" } ] } diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f317161..9e3939f 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -23,6 +23,18 @@ The hook fails closed. In a linked worktree, the first guarded call should resto If the worktree expects a different Boatstack version or source commit, update or rebase its committed Boatstack infrastructure. Boatstack will not run a newer cached helper against an older worktree contract. +## Cursor reports `MainThreadShellExec not initialized` + +This is a Cursor host initialization failure: Boatstack's hook process did not start. Keep the hook fail-closed, run **Developer: Reload Window** in Cursor, and retry the Boatstack operation. Do not reinstall Boatstack for this error alone. Reinstall only when Boatstack itself reports a missing, drifted, unsafe, or checksum-invalid helper or shared runtime. + +## `/repair` says there is no active delivery + +Repair compares an exact requested change with an activated, approved baseline. If no Boatstack feature has started, save the host Plan-mode file and run `/auto-plan`. If a draft or approved feature already exists, run the one planning or build operation reported by `/boatstack-next`; do not create or clear delivery state manually. + +## Boatstack reports invalid or orphaned delivery state + +Preserve the named plan, lock, preview, receipts, and managed state. A missing `plan.lock.json`, stale lock hash, or orphan `pr.md` cannot be repaired by choosing the newest artifact or deleting state. Restore the missing tracked evidence from version control or the originating feature branch, then rerun `/boatstack-next`. If the evidence cannot be restored, stop and prepare a separately reviewed recovery rather than resetting progress in place. + ## Cursor cannot find a slash command Cursor reads project commands from `.cursor/commands/*.md`: diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index 5f9ad5c..ee2cc6d 100644 --- a/labs/diagram-json/plan.lock.json +++ b/labs/diagram-json/plan.lock.json @@ -6,7 +6,7 @@ "plan_path": "labs/diagram-json/plan.md", "plan_sha256": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "schema_version": 1, - "source_commit": "ae167a3ce7759793d4dfbd710eea7ecbbf577058", + "source_commit": "af3a40320dd2525fccc99e67c7ca26a43ec7ded8", "source_plan_path": "labs/diagram-json/source-plan.md", "source_plan_sha256": "e10593ddaa7522ab80cc991d0a09399257139799e37f737794cd49d68a39985b", "spec_path": "labs/diagram-json/spec.md", diff --git a/release-notes/2026-07-18-startup-recovery-routing.md b/release-notes/2026-07-18-startup-recovery-routing.md new file mode 100644 index 0000000..af2e978 --- /dev/null +++ b/release-notes/2026-07-18-startup-recovery-routing.md @@ -0,0 +1,3 @@ +### Startup and repair recovery follows verified feature state + +`boatstack run` now enters planning when one saved Plan-mode file exists and explains how to start when none exists. Repair routes pre-build work to the verified planning or build step instead of asking for a change without an approved baseline. Invalid or orphaned delivery evidence is reported without clearing artifacts, and Cursor's `MainThreadShellExec not initialized` failure now points to window reload rather than unnecessary Boatstack reinstallation.