From df25a8646502237db58bb7eb4b91f063ed211a01 Mon Sep 17 00:00:00 2001 From: "operator-stack-publisher[bot]" Date: Sat, 18 Jul 2026 20:01:40 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ 569709dad427 --- CONTRIBUTING.md | 2 +- README.md | 3 + UPSTREAM.json | 35 ++-- boatstack/SKILL.md | 5 + boatstack/cmd/boatstack-helper/main.go | 27 +++- boatstack/export.go | 9 +- boatstack/export_test.go | 5 +- boatstack/init.go | 2 + boatstack/init_test.go | 1 + boatstack/next.go | 213 +++++++++++++++++++++++++ boatstack/next_test.go | 163 +++++++++++++++++++ boatstack/references/workflow.md | 1 + docs/evidence-engineered-coding.md | 4 +- docs/getting-started.md | 11 ++ docs/public-claims.json | 24 +-- labs/diagram-json/plan.lock.json | 2 +- release-notes/2026-07-18-next-stage.md | 3 + 17 files changed, 473 insertions(+), 37 deletions(-) create mode 100644 boatstack/next.go create mode 100644 boatstack/next_test.go create mode 100644 release-notes/2026-07-18-next-stage.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 58b1324..695cc62 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/bbed9588e74016250e3cf8968e70bdd375afb863/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/569709dad427c28f7e866a1bd72ca0312d7b2f1b/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 802bf3b..306c783 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ 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**. + 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`. > The diagram shows what Boatstack guides—not a checklist you need to memorize. @@ -72,6 +74,7 @@ Receipts remain as history; published corrections become linked deliveries. ## Features - **A guided path from idea to PR.** Start with `/auto-plan`; Boatstack presents one next action at a time through planning, approval, build, validation, review, and PR preparation. +- **Instant orientation after a break.** `boatstack next` reconstructs the verified stage without treating chat or a running process as workflow evidence. - **Human decisions stay human.** Material product questions remain open until a person answers them, and implementation waits for explicit approval. - **Evidence tied to the promise.** Tests and checks map to the outcomes the change claims to deliver instead of treating one green command as proof of everything. - **Context that survives the feature.** Plans, decisions, accepted gaps, evidence, review findings, and code state can inform the next feature rather than disappearing with the chat. diff --git a/UPSTREAM.json b/UPSTREAM.json index e87b449..61b3ba3 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -1,7 +1,7 @@ { "canonical_context": { - "characters": 39803, - "estimated_tokens": 9951, + "characters": 40064, + "estimated_tokens": 10016, "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": "6c29c112507af7f6661d7d3485da1f7974462b85f54b1075a014fdb5b9a4e546", - "README.md": "7d1473b75566901899b49a9f98cd5446ac40d2fceee3f168c5e6869c742e0b4a", + "CONTRIBUTING.md": "4512dadf5f809ce83323766bd62b8b85498718273d21a498635bfc0aca1afbc0", + "README.md": "b624739fd939fe1e94c2c75bc5e30f9517214fdd4c518b8830975257cd22f072", "assets/boatstack-journey.svg": "c1f7fe2741f5e9ca66bb3fe9b103e6364ba5acbca8b7a8054768ffd85cf325ea", "assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4", "assets/boatstack-portability.svg": "ce648f5581d16586d25824d3a8132ef1b3d88b73329179173120129d4f74fd24", - "boatstack/SKILL.md": "9443685e255a59de2f5e58928051b4f7385f7401831300b78376d48e41024ba1", + "boatstack/SKILL.md": "a6dc11f9010d2029d7ed1ee3f8f1555988c8ec7e603b6eb83b48ed6c7748af5d", "boatstack/agents/openai.yaml": "68a30a60859556c5a26e16d184594ca243a6043d99c8cf7d66b5dd6d50a93cd1", "boatstack/assets/templates/adr.md": "c577a3c1c1319061f61deb053597e6e853657022185fe28b8f733327e2a78565", "boatstack/assets/templates/approval.md": "74b0b816703a6dce3c96c8f95f981af910b020b6908e7f76cf5630778637e9f5", @@ -31,18 +31,20 @@ "boatstack/assets/templates/test-plan.md": "6db8a9f27dd171fb80222a501cae50eb051e7278c04703fa43b5ff86dd4d2df4", "boatstack/atomic_unix.go": "89f2723361591de2bb8bd22ce7e34ec529d3278509f0df78fd5c4a7d4140fbe9", "boatstack/atomic_windows.go": "cefd775cbe7e7c3bd8a3f5673b11cdd784c6d3ebd6de7dcb8f39406b0bee511f", - "boatstack/cmd/boatstack-helper/main.go": "fc979a21007107f6a2f52a2c1eb82c35be5dbe97a775a908e0d1ebc25864f040", + "boatstack/cmd/boatstack-helper/main.go": "dc30a087003f5a1e432d69052178a7594d544b91a75cb0b99881a6f67c451260", "boatstack/delivery.go": "907a0ff8dc3e6120387eef3c7d97cdadb0b9dd8f788cb39e149aa7a6c4a6260a", "boatstack/delivery_test.go": "744d166757deafe5b9fc4f66b79b324de43dcc36a26a317572eaec73a8b21044", - "boatstack/export.go": "72ed801fcf52fc93e8446dd3b7d205d7ef8a0530b33f4e02891f7290bc243343", - "boatstack/export_test.go": "8f04947a5e1723df21f068b14ba295b0281a04b5037d251be0315100bdce65e8", + "boatstack/export.go": "c78647482118b80eb6e8283a3939234d400f324f9f576d069317dfb7d2046ed3", + "boatstack/export_test.go": "fb6d85a984e5e03e21dcb0878fc89dc6ec9ea5e12467a21495287784bf31bf6e", "boatstack/go.mod": "57c377eccea51372d6664de4169e2ca45806b046f7e8a98a1e35a9eb454b4b8d", "boatstack/hooks.go": "1d5d8c4bf7e6e867c8bf07e391d86158347269f856647a5d256f345bbb8d3c96", "boatstack/hooks_test.go": "c3f359416ea53f258d8747d0247381e8946efd4d4a5bcf072c4147f885475ad3", - "boatstack/init.go": "3bc5b94f662c855f32ccf7db021d4783bad75e3c8c2ed7b384e3a9c60ccd4096", - "boatstack/init_test.go": "090cdde8541459330774e6b7ba17f6a0fa2c44e3a4dc7151dccb5d53b535a482", + "boatstack/init.go": "fb863a68a6cced5bd2dcf9cd8ca53f0dab7ef0309830269efd985ad8b8a92059", + "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/plan_test.go": "006cdc6681f77e579c5a0f709e30ede759c337132d4f2f5193b7b79b29bd7149", "boatstack/planning.go": "3a26417a295e5dfc2b6dcac702287c04b6053e7b74215858a4ea11cf9f9dadfe", @@ -53,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": "6794ef2899e3cde7f8cdf182b65def8e371ae3ec3d38cc3cbe2009adb4321512", + "boatstack/references/workflow.md": "c604f5e6372a1f32adf4e49360395bfba6869a249db2e077cf678792c0241eac", "boatstack/release.go": "fa2ac926df89c90c5844e938a2e02d4b8dbbaefbf85bb7a1a89fc51690bea520", "boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690", "boatstack/runtime.go": "f393745950e8ba2da7e25d5539ad536a6239a10c17109224bad20cf48445c380", @@ -69,10 +71,10 @@ "docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3", "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", - "docs/evidence-engineered-coding.md": "d9397b0ddb7ede2080a1a4d703b3272dcf31080cfc0f7b1cc732c847145386ee", + "docs/evidence-engineered-coding.md": "9a6e1770ed903a22ade4be804a35e5fc0a557d369b438b449cce967b761f6943", "docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c", - "docs/getting-started.md": "4ef77719a01a2dbe98c5154d58fc03f51797ae0ef39a09d8804fe0708b8cc920", - "docs/public-claims.json": "67f2b7c4a288ba11338b11e5db2cb7efa7e498c42dcc3264da641ecbadd59f2a", + "docs/getting-started.md": "2a642a8ef072502f1a099e211cb599a0179023d1b48b41b4d6b407881494e594", + "docs/public-claims.json": "e68306b85634d7868ca9697da1e7491fd72abe0ba3eb0706a958b35b06930afb", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -86,7 +88,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": "4fc2cba6a3a4bbe37d6e3591b583c023dccc8fff778ec00fd2c8ef789d6a6b0e", + "labs/diagram-json/plan.lock.json": "50e07d4290abd9c122a300f7f5aa443d8e829ea5010abbf8ddf0711d2497b576", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -105,13 +107,14 @@ "release-notes/2026-07-18-harbor-lab-namespace.md": "6419c049e5a3024c5a8604e4d9fb241c27ceb80bf1d4cc62f66d1a0eaf09ea21", "release-notes/2026-07-18-host-hook-migrations.md": "1c9f81d9318854214f72802045e8e39c9ca45435af0f9d2c28fcf4ff4c1e0071", "release-notes/2026-07-18-intelligence-flow-labs.md": "b236dddcf22dab718698b05c5dcf162ffddf9f5b53ea98468fd49b342d75edb9", + "release-notes/2026-07-18-next-stage.md": "42ac8e9e45303fe5609cdd3fde10ca69ed349b2d4948445ed91d0e49d5c769b0", "release-notes/2026-07-18-safety-sql-boundaries.md": "32011ca3d02a371e8f3f2899ffb34df3af0843d18d25e7db95fbca32c2dcf18c", "release-notes/2026-07-18-stacked-bar-mark.md": "c4d5bd5fb89c280d7fba015384fd795fcb8c31ffe501078aa55a90cbcf66ba7b" }, "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "bbed9588e74016250e3cf8968e70bdd375afb863", + "commit": "569709dad427c28f7e866a1bd72ca0312d7b2f1b", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/SKILL.md b/boatstack/SKILL.md index 0cec3da..c3838c7 100644 --- a/boatstack/SKILL.md +++ b/boatstack/SKILL.md @@ -12,6 +12,7 @@ Build the smallest complete product slice that can be independently verified. Im Map the request to one operation: - `init`: inspect a repository and create or update `.product-loop/project.json`. +- `next`: report the verified current stage and exactly one next action without changing workflow or repository state. - `auto-plan`: refine a saved host Plan-mode file into a reviewable draft feature package; refuse when that file is absent. - `plan-gate`: validate the Markdown draft, present it for explicit human acceptance, and record that acceptance in Markdown. - `build`: activate the approved Markdown plan, then implement only the active delivery slice's tasks. @@ -25,6 +26,10 @@ Map the request to one operation: For the full state machine, read [workflow.md](references/workflow.md). For artifact meanings and templates, read [artifacts.md](references/artifacts.md). +## 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. + ## Enforce the irreversible-operation boundary Read [irreversible-operation-boundary.md](references/irreversible-operation-boundary.md). Project hooks hard-deny high-confidence destructive shell and MCP operations on every supported agent call. Never request or invent an in-session bypass. After an external-write failure, preserve state, use read-only diagnosis, retain the immutable target boundary, and choose only proven transactional retry or fix-forward recovery. Source edits may be reviewed, but an executable destructive capability blocks activation and every later gate. diff --git a/boatstack/cmd/boatstack-helper/main.go b/boatstack/cmd/boatstack-helper/main.go index ce3914b..c425719 100644 --- a/boatstack/cmd/boatstack-helper/main.go +++ b/boatstack/cmd/boatstack-helper/main.go @@ -302,6 +302,29 @@ func deliveryStatusCommand(arguments []string) int { return 0 } +func nextStatusCommand(arguments []string) int { + flags := flag.NewFlagSet("next-status", flag.ContinueOnError) + repo := flags.String("repo", ".", "repository whose Boatstack stage should be inspected") + jsonOutput := flags.Bool("json", false, "print the versioned structured status") + if err := flags.Parse(arguments); err != nil { + return 2 + } + status, err := boatstack.ResolveNext(*repo) + if err != nil { + return fail(err) + } + if *jsonOutput { + value, marshalErr := boatstack.MarshalJSON(status) + if marshalErr != nil { + return fail(marshalErr) + } + fmt.Print(string(value)) + } else { + fmt.Print(boatstack.FormatNextStatus(status)) + } + return 0 +} + func recordChangeCommand(arguments []string) int { flags := flag.NewFlagSet("record-change", flag.ContinueOnError) options := boatstack.ChangeObservationOptions{} @@ -489,7 +512,7 @@ func publishPRCommand(arguments []string) int { func run() int { if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "usage: boatstack-helper ") + fmt.Fprintln(os.Stderr, "usage: boatstack-helper ") return 2 } switch os.Args[1] { @@ -517,6 +540,8 @@ func run() int { return activatePlanCommand(os.Args[2:]) case "delivery-status": return deliveryStatusCommand(os.Args[2:]) + case "next-status": + return nextStatusCommand(os.Args[2:]) case "record-change": return recordChangeCommand(os.Args[2:]) case "record-delivery-gate": diff --git a/boatstack/export.go b/boatstack/export.go index 0ad3334..d495213 100644 --- a/boatstack/export.go +++ b/boatstack/export.go @@ -36,6 +36,10 @@ type claudeSkillSpec struct { } var claudeVisibleSkills = []claudeSkillSpec{ + { + Name: "boatstack-next", + Description: "Report the verified Boatstack stage and exactly one next action without changing state.", + }, { Name: "auto-plan", Description: "Refine one saved Plan-mode proposal into a reviewable Boatstack feature plan.", @@ -226,6 +230,7 @@ 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.", "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.", @@ -269,12 +274,12 @@ Boatstack's repository hooks deny high-confidence irreversible operations across adapterSkill := fmt.Sprintf(`--- name: %s -description: Use when the user asks Boatstack to auto-plan, approve a plan, build, repair or modify an active delivery, test, review, ship, update Boatstack, or run a retrospective. Also use automatically when ordinary free-form change language targets an active managed delivery. + description: Use when the user asks what is next in Boatstack, or asks Boatstack to auto-plan, repair, approve a plan, build, test, review, ship, update Boatstack, or run a retrospective. Also use automatically when ordinary free-form change language targets an active managed delivery. --- # Boatstack adapter -Read .product-loop/project.json and .product-loop/workflow.md. Valid operations are auto-plan, plan-gate, build, repair, test-gate, review-gate/review, ship-gate/ship, boatstack-update, and retro. Before any product edit, check for an active managed delivery. If one exists and ordinary user language reports a problem or asks for a modification, automatically use repair even when the user did not name the operation. + Read .product-loop/project.json and .product-loop/workflow.md. The requested operation is supplied by the user; valid operations are next, boatstack-next, auto-plan, plan-gate, build, repair, test-gate, review-gate/review, ship-gate/ship, boatstack-update, and retro. Route next and natural-language questions such as "what's next in Boatstack?" to the read-only boatstack-next operation. Before any product edit, check for an active managed delivery. If one exists and ordinary user language reports a problem or asks for a modification, automatically use repair even when the user did not name the operation. Follow the User-facing response contract in .product-loop/workflow.md for every operation. Lead with the mapped plain-language outcome, show only decision-relevant content, end with exactly one Next step, and move machine statuses, helper output, fingerprints, artifact paths, receipts, and locks into collapsed Technical details. Internal helper names must not appear in the primary response. diff --git a/boatstack/export_test.go b/boatstack/export_test.go index 7bef03f..5f07304 100644 --- a/boatstack/export_test.go +++ b/boatstack/export_test.go @@ -98,6 +98,7 @@ func TestExportAndDriftCheck(t *testing.T) { t.Fatal(err) } for _, path := range []string{ + ".cursor/commands/boatstack-next.md", ".cursor/commands/boatstack-update.md", ".cursor/commands/repair.md", ".cursor/commands/plan-gate.md", @@ -356,7 +357,7 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { workflow := string(bundle.Files[".product-loop/workflow.md"]) artifacts := string(bundle.Files[".product-loop/artifacts.md"]) - for _, expected := range []string{"auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { + for _, expected := range []string{"boatstack-next", "auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { if !strings.Contains(workflow, expected) { t.Fatalf("canonical portable workflow is missing %q", expected) } @@ -387,7 +388,7 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { } } } - for _, operation := range []string{"auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { + for _, operation := range []string{"next", "boatstack-next", "auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { if !strings.Contains(hostSurfaces["codex"], operation) { t.Fatalf("Codex router does not declare portable operation %q", operation) } diff --git a/boatstack/init.go b/boatstack/init.go index 630a867..94d9392 100644 --- a/boatstack/init.go +++ b/boatstack/init.go @@ -561,6 +561,8 @@ func RunInit(options InitOptions) (returnErr error) { fmt.Fprintln(options.Output, " Claude Code: /auto-plan") fmt.Fprintln(options.Output, " Cursor: /auto-plan") fmt.Fprintln(options.Output, " Codex: $boatstack auto-plan") + fmt.Fprintln(options.Output, "Return later with Claude Code or Cursor: /boatstack-next") + fmt.Fprintln(options.Output, "Return later with Codex: $boatstack next") fmt.Fprintln(options.Output, "If Boatstack created .claude/skills during an active Claude Code session, reload Claude Code before using its slash commands.") if err := snapshot.commit(); err != nil { return fmt.Errorf("remove initialization rollback snapshot: %w", err) diff --git a/boatstack/init_test.go b/boatstack/init_test.go index 62a7fbc..2c42ff8 100644 --- a/boatstack/init_test.go +++ b/boatstack/init_test.go @@ -56,6 +56,7 @@ func TestRuntimeFreeInit(t *testing.T) { } for _, expected := range []string{ "Boatstack start command by host:", + "Codex: $boatstack next", "Claude Code: /auto-plan", "Cursor: /auto-plan", "Codex: $boatstack auto-plan", diff --git a/boatstack/next.go b/boatstack/next.go new file mode 100644 index 0000000..f290d5e --- /dev/null +++ b/boatstack/next.go @@ -0,0 +1,213 @@ +package boatstack + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "strings" +) + +const nextStatusSchemaVersion = 1 + +// NextStatus is the read-only, host-neutral projection of Boatstack's current +// workflow position. Conversation and terminal context are deliberately absent: +// adapters may present them as context, but they are not workflow evidence. +type NextStatus struct { + SchemaVersion int `json:"schema_version"` + VerificationStatus string `json:"verification_status"` + Feature string `json:"feature,omitempty"` + ActiveSlice string `json:"active_slice,omitempty"` + ObservedStage string `json:"observed_stage"` + NextOperation string `json:"next_operation"` + Reason string `json:"reason"` + BlockingAmbiguity []string `json:"blocking_ambiguity,omitempty"` +} + +func featurePlanCandidates(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 + } + features := []string{} + for _, entry := range entries { + if entry.IsDir() && featureSlugPattern.MatchString(entry.Name()) && fileExists(filepath.Join(root, entry.Name(), "plan.md")) { + features = append(features, entry.Name()) + } + } + sort.Strings(features) + return features, nil +} + +func nextForDelivery(repo, feature string) (NextStatus, error) { + state, err := CurrentDeliveryState(repo, feature) + if err != nil { + return NextStatus{}, err + } + slice, err := activeDeliverySlice(state) + if err != nil { + return NextStatus{}, err + } + status := NextStatus{ + SchemaVersion: nextStatusSchemaVersion, VerificationStatus: "VERIFIED", + Feature: feature, ActiveSlice: slice.ID, ObservedStage: slice.Status, + } + switch slice.Status { + case "BUILD": + status.NextOperation = "build" + status.Reason = "The approved delivery slice is active and has no current test-gate receipt." + case "TEST_PASSED": + status.NextOperation = "review-gate" + status.Reason = "The active delivery slice has current test evidence and still requires review." + case "REVIEW_PASSED": + previewPath := filepath.Join(repo, ".product-loop", "features", feature, "pr.md") + if preview, previewErr := ParsePRPreview(previewPath); previewErr == nil && preview.Feature == feature && preview.SliceID == slice.ID { + status.ObservedStage = "PR_PREVIEW" + status.Reason = "A reviewer-ready PR preview exists for the reviewed active slice and must be reconfirmed through the ship gate." + } else { + status.Reason = "The active delivery slice has current test and review evidence and is ready for PR preparation." + } + status.NextOperation = "ship-gate" + default: + return NextStatus{}, fmt.Errorf("managed delivery slice %s has unsupported status %q", slice.ID, slice.Status) + } + return status, nil +} + +func completedManagedStates(repo string) ([]DeliveryState, error) { + directory, err := deliveryStateDirectory(repo) + if err != nil { + return nil, err + } + entries, err := os.ReadDir(directory) + if os.IsNotExist(err) { + return nil, nil + } + if err != nil { + return nil, err + } + completed := []DeliveryState{} + for _, entry := range entries { + if !entry.IsDir() || !featureSlugPattern.MatchString(entry.Name()) { + continue + } + state, err := CurrentDeliveryState(repo, entry.Name()) + if err != nil { + return nil, fmt.Errorf("invalid managed delivery state for %s: %w", entry.Name(), err) + } + if state.ActiveIndex >= len(state.Slices) { + completed = append(completed, state) + } + } + sort.Slice(completed, func(i, j int) bool { return completed[i].Feature < completed[j].Feature }) + return completed, nil +} + +// ResolveNext performs bounded, local, read-only state inspection. It never +// contacts GitHub and never treats process or conversation history as evidence. +func ResolveNext(repoPath string) (NextStatus, error) { + repo, err := ResolveRepository(repoPath) + if err != nil { + return NextStatus{}, err + } + base := NextStatus{SchemaVersion: nextStatusSchemaVersion} + if !fileExists(filepath.Join(repo, ".product-loop", "project.json")) { + base.VerificationStatus = "UNVERIFIED" + base.ObservedStage = "NOT_INITIALIZED" + base.NextOperation = "init" + base.Reason = "This repository has no Boatstack project installation to inspect." + return base, nil + } + + active, err := ActiveManagedDeliveries(repo) + if err != nil { + return NextStatus{}, err + } + if len(active) > 1 { + base.VerificationStatus = "BLOCKED" + base.ObservedStage = "AMBIGUOUS" + base.NextOperation = "resolve-ambiguity" + base.Reason = "More than one managed delivery is active; Boatstack will not choose by recency." + base.BlockingAmbiguity = active + 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." + } + return base, nil + } + + candidates, err := featurePlanCandidates(repo) + if err != nil { + return NextStatus{}, err + } + if len(candidates) > 1 { + base.VerificationStatus = "BLOCKED" + base.ObservedStage = "AMBIGUOUS" + base.NextOperation = "resolve-ambiguity" + base.Reason = "More than one saved feature plan is available; Boatstack will not choose by recency." + base.BlockingAmbiguity = candidates + return base, nil + } + if len(candidates) == 1 { + feature := candidates[0] + directory := filepath.Join(repo, ".product-loop", "features", feature) + base.VerificationStatus = "VERIFIED" + base.Feature = feature + if fileExists(filepath.Join(directory, "approval.md")) { + base.ObservedStage = "APPROVED" + base.NextOperation = "build" + base.Reason = "The saved feature has an approval receipt but no active delivery state." + } else { + base.ObservedStage = "DRAFT_PLAN" + base.NextOperation = "plan-gate" + base.Reason = "The saved feature plan has not been approved." + } + return base, nil + } + + base.VerificationStatus = "VERIFIED" + base.ObservedStage = "FEATURE_COMPLETE" + base.NextOperation = "none" + base.Reason = "No managed Boatstack plan or delivery remains active." + return base, nil +} + +func FormatNextStatus(status NextStatus) string { + parts := []string{ + "Boatstack stage: " + status.ObservedStage, + "Verification: " + status.VerificationStatus, + } + if status.Feature != "" { + parts = append(parts, "Feature: "+status.Feature) + } + if status.ActiveSlice != "" { + parts = append(parts, "Active slice: "+status.ActiveSlice) + } + parts = append(parts, "Reason: "+status.Reason, "Next: "+status.NextOperation) + if len(status.BlockingAmbiguity) > 0 { + parts = append(parts, "Candidates: "+strings.Join(status.BlockingAmbiguity, ", ")) + } + return strings.Join(parts, "\n") + "\n" +} diff --git a/boatstack/next_test.go b/boatstack/next_test.go new file mode 100644 index 0000000..12c5801 --- /dev/null +++ b/boatstack/next_test.go @@ -0,0 +1,163 @@ +package boatstack + +import ( + "os" + "os/exec" + "path/filepath" + "reflect" + "testing" +) + +func nextTestRepo(t *testing.T) string { + t.Helper() + repo := t.TempDir() + if output, err := exec.Command("git", "-C", repo, "init").CombinedOutput(); err != nil { + t.Fatalf("git init: %v: %s", err, output) + } + if err := os.MkdirAll(filepath.Join(repo, ".product-loop", "features"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(repo, ".product-loop", "project.json"), []byte("{}\n"), 0o644); err != nil { + t.Fatal(err) + } + return repo +} + +func writeNextDelivery(t *testing.T, repo, feature, status string, activeIndex int) { + t.Helper() + directory := filepath.Join(repo, ".product-loop", "features", feature) + if err := os.MkdirAll(directory, 0o755); err != nil { + t.Fatal(err) + } + lockPath := filepath.Join(directory, "plan.lock.json") + if err := os.WriteFile(lockPath, []byte("lock\n"), 0o644); err != nil { + t.Fatal(err) + } + hash, err := SHA256File(lockPath) + if err != nil { + t.Fatal(err) + } + if err := saveDeliveryState(repo, DeliveryState{ + SchemaVersion: deliveryStateSchemaVersion, Feature: feature, PlanLockHash: hash, + ActiveIndex: activeIndex, Slices: []DeliverySlice{{ID: "delivery", Title: "Delivery", Status: status}}, + }); err != nil { + t.Fatal(err) + } +} + +func TestResolveNextReportsFeatureCompleteWhenNothingIsActive(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" { + t.Fatalf("unexpected status: %+v", status) + } +} + +func TestResolveNextPlanningStates(t *testing.T) { + for _, test := range []struct { + name, approval, stage, next string + }{ + {name: "draft", stage: "DRAFT_PLAN", next: "plan-gate"}, + {name: "approved", approval: "approved", stage: "APPROVED", next: "build"}, + } { + t.Run(test.name, func(t *testing.T) { + repo := nextTestRepo(t) + directory := filepath.Join(repo, ".product-loop", "features", "recovery") + 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) + } + if test.approval != "" { + if err := os.WriteFile(filepath.Join(directory, "approval.md"), []byte("# Approval\n"), 0o644); err != nil { + t.Fatal(err) + } + } + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + if status.ObservedStage != test.stage || status.NextOperation != test.next || status.Feature != "recovery" { + t.Fatalf("unexpected status: %+v", status) + } + }) + } +} + +func TestResolveNextDeliveryTransitions(t *testing.T) { + for _, test := range []struct{ state, next string }{ + {state: "BUILD", next: "build"}, + {state: "TEST_PASSED", next: "review-gate"}, + {state: "REVIEW_PASSED", next: "ship-gate"}, + } { + t.Run(test.state, func(t *testing.T) { + repo := nextTestRepo(t) + writeNextDelivery(t, repo, "recovery", test.state, 0) + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + if status.ObservedStage != test.state || status.NextOperation != test.next || status.ActiveSlice != "delivery" { + t.Fatalf("unexpected status: %+v", status) + } + }) + } +} + +func TestResolveNextReportsFeatureCompleteAfterPublication(t *testing.T) { + repo := nextTestRepo(t) + writeNextDelivery(t, repo, "recovery", "PUBLISHED", 1) + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + if status.ObservedStage != "FEATURE_COMPLETE" || status.NextOperation != "none" { + t.Fatalf("unexpected status: %+v", status) + } + if status.Feature != "recovery" { + t.Fatalf("expected recovery feature to be marked complete: %+v", status) + } + if status.ActiveSlice != "delivery" { + t.Fatalf("expected final delivery slice to be surfaced: %+v", status) + } +} + +func TestResolveNextBlocksMultipleActiveFeaturesWithoutMutation(t *testing.T) { + repo := nextTestRepo(t) + writeNextDelivery(t, repo, "first", "BUILD", 0) + writeNextDelivery(t, repo, "second", "BUILD", 0) + before, err := os.ReadFile(filepath.Join(repo, ".git", "boatstack", "deliveries", "first", "state.json")) + if err != nil { + t.Fatal(err) + } + status, err := ResolveNext(repo) + if err != nil { + t.Fatal(err) + } + after, err := os.ReadFile(filepath.Join(repo, ".git", "boatstack", "deliveries", "first", "state.json")) + if err != nil { + t.Fatal(err) + } + if status.VerificationStatus != "BLOCKED" || !reflect.DeepEqual(status.BlockingAmbiguity, []string{"first", "second"}) { + t.Fatalf("unexpected ambiguity: %+v", status) + } + if !reflect.DeepEqual(before, after) { + t.Fatal("read-only next inspection changed delivery state") + } +} + +func TestResolveNextRejectsStaleManagedState(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") + } +} diff --git a/boatstack/references/workflow.md b/boatstack/references/workflow.md index 754fd51..a4bfd9d 100644 --- a/boatstack/references/workflow.md +++ b/boatstack/references/workflow.md @@ -65,6 +65,7 @@ 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 | | `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 | diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 604d315..dd2b864 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 **9951 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 **10016 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 [`bbed9588e74016250e3cf8968e70bdd375afb863`](https://github.com/operatorstack/intelligence-flow/tree/bbed9588e74016250e3cf8968e70bdd375afb863/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 [`569709dad427c28f7e866a1bd72ca0312d7b2f1b`](https://github.com/operatorstack/intelligence-flow/tree/569709dad427c28f7e866a1bd72ca0312d7b2f1b/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/getting-started.md b/docs/getting-started.md index 640cea1..f026d79 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,6 +7,17 @@ Boatstack is repository-local. Install it once per Git clone and commit the shared workflow before starting product work. Linked Git worktrees reuse the clone's verified runtime automatically. +## Return after an interruption + +Ask Boatstack for the next verified stage without changing anything: + +| Host | Command | +|---|---| +| Codex | `$boatstack next` | +| Cursor or Claude Code | `/boatstack-next` | + +Boatstack reads repository-owned plans, approvals, delivery state, and gate receipts, then returns exactly one next action. Chat, terminal, worktree, and running-process observations may add context but never establish a workflow stage. If no managed work remains, Boatstack reports **Feature complete** and **No action required**. + ## 1. Install it separately The easiest path is to paste the [agent installation prompt](../README.md#install-with-your-coding-agent) into your coding host. It asks the agent to create `chore/install-boatstack`, run the official installer, explain the generated files, run `doctor`, and prepare the installation PR without merging it. diff --git a/docs/public-claims.json b/docs/public-claims.json index c675e2f..50e8e05 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "bbed9588e74016250e3cf8968e70bdd375afb863", + "source_commit": "569709dad427c28f7e866a1bd72ca0312d7b2f1b", "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" }, { "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:bbed9588e74016250e3cf8968e70bdd375afb863" + "last_verified_version": "source:569709dad427c28f7e866a1bd72ca0312d7b2f1b" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index c5cb08e..313b323 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": "bbed9588e74016250e3cf8968e70bdd375afb863", + "source_commit": "569709dad427c28f7e866a1bd72ca0312d7b2f1b", "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-next-stage.md b/release-notes/2026-07-18-next-stage.md new file mode 100644 index 0000000..89947a5 --- /dev/null +++ b/release-notes/2026-07-18-next-stage.md @@ -0,0 +1,3 @@ +### Find the next Boatstack stage after an interruption + +Run `$boatstack next` in Codex or `/boatstack-next` in Cursor and Claude Code to recover the verified workflow stage and one next action without changing repository or delivery state. When no managed work remains, Boatstack now reports **Feature complete**.