diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 695cc62..2efd8e4 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/569709dad427c28f7e866a1bd72ca0312d7b2f1b/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/ae167a3ce7759793d4dfbd710eea7ecbbf577058/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 306c783..89e29f3 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ That is all you need to learn up front. Boatstack shows one next action at a tim 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**. +`$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. + 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. diff --git a/UPSTREAM.json b/UPSTREAM.json index 61b3ba3..52bc589 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -1,7 +1,7 @@ { "canonical_context": { - "characters": 40064, - "estimated_tokens": 10016, + "characters": 41894, + "estimated_tokens": 10474, "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": "4512dadf5f809ce83323766bd62b8b85498718273d21a498635bfc0aca1afbc0", - "README.md": "b624739fd939fe1e94c2c75bc5e30f9517214fdd4c518b8830975257cd22f072", + "CONTRIBUTING.md": "fb54f9f378de5d12f0f2f4fdbd8136cfd7c89142966884d26da448973f738eae", + "README.md": "4526095079a7063ca6f4c7db367447c11be607bf86348ba7c5b94f080b7724ec", "assets/boatstack-journey.svg": "c1f7fe2741f5e9ca66bb3fe9b103e6364ba5acbca8b7a8054768ffd85cf325ea", "assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4", "assets/boatstack-portability.svg": "ce648f5581d16586d25824d3a8132ef1b3d88b73329179173120129d4f74fd24", - "boatstack/SKILL.md": "a6dc11f9010d2029d7ed1ee3f8f1555988c8ec7e603b6eb83b48ed6c7748af5d", + "boatstack/SKILL.md": "44657cc7a55359bfd0025eae501e31b829f9665005a02d24f3b6ccbef3db3bf0", "boatstack/agents/openai.yaml": "68a30a60859556c5a26e16d184594ca243a6043d99c8cf7d66b5dd6d50a93cd1", "boatstack/assets/templates/adr.md": "c577a3c1c1319061f61deb053597e6e853657022185fe28b8f733327e2a78565", "boatstack/assets/templates/approval.md": "74b0b816703a6dce3c96c8f95f981af910b020b6908e7f76cf5630778637e9f5", @@ -31,11 +31,11 @@ "boatstack/assets/templates/test-plan.md": "6db8a9f27dd171fb80222a501cae50eb051e7278c04703fa43b5ff86dd4d2df4", "boatstack/atomic_unix.go": "89f2723361591de2bb8bd22ce7e34ec529d3278509f0df78fd5c4a7d4140fbe9", "boatstack/atomic_windows.go": "cefd775cbe7e7c3bd8a3f5673b11cdd784c6d3ebd6de7dcb8f39406b0bee511f", - "boatstack/cmd/boatstack-helper/main.go": "dc30a087003f5a1e432d69052178a7594d544b91a75cb0b99881a6f67c451260", + "boatstack/cmd/boatstack-helper/main.go": "b4b8b43d80dbf60f15e18885ff8ee01637df6e47c0249938714d885163350539", "boatstack/delivery.go": "907a0ff8dc3e6120387eef3c7d97cdadb0b9dd8f788cb39e149aa7a6c4a6260a", "boatstack/delivery_test.go": "744d166757deafe5b9fc4f66b79b324de43dcc36a26a317572eaec73a8b21044", - "boatstack/export.go": "c78647482118b80eb6e8283a3939234d400f324f9f576d069317dfb7d2046ed3", - "boatstack/export_test.go": "fb6d85a984e5e03e21dcb0878fc89dc6ec9ea5e12467a21495287784bf31bf6e", + "boatstack/export.go": "d8117e8c2da549a9a5e96794440f163053d82a4e945624a11f168133f430a761", + "boatstack/export_test.go": "d98aa90e24bb4dbe7947ab36243a36cb967889456186e5a775abd3d999c779e5", "boatstack/go.mod": "57c377eccea51372d6664de4169e2ca45806b046f7e8a98a1e35a9eb454b4b8d", "boatstack/hooks.go": "1d5d8c4bf7e6e867c8bf07e391d86158347269f856647a5d256f345bbb8d3c96", "boatstack/hooks_test.go": "c3f359416ea53f258d8747d0247381e8946efd4d4a5bcf072c4147f885475ad3", @@ -55,9 +55,11 @@ "boatstack/references/failure-moves.md": "1d35126348d0b681976e8819665e16fd745fd65eca271492603cb80aab75bf49", "boatstack/references/irreversible-operation-boundary.md": "2a695f2d7de95cfc8750f107bef9c86581712aa1f02e7233b69b850d8c2af42e", "boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae", - "boatstack/references/workflow.md": "c604f5e6372a1f32adf4e49360395bfba6869a249db2e077cf678792c0241eac", + "boatstack/references/workflow.md": "9edba7fe6cac8a4f67120058ba3c567a86a5982c49c65758b00dcfbb6dacfb13", "boatstack/release.go": "fa2ac926df89c90c5844e938a2e02d4b8dbbaefbf85bb7a1a89fc51690bea520", "boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690", + "boatstack/run.go": "a9afb239c5e2cb80f96dab594f0dc94852f2f16625b1de05c5110ee85f61cfaa", + "boatstack/run_test.go": "fdc416f15e787b5c8401fc0f0e3aeb58b4891c828a8a869c8dce4e8f1541d809", "boatstack/runtime.go": "f393745950e8ba2da7e25d5539ad536a6239a10c17109224bad20cf48445c380", "boatstack/runtime_cache.go": "60c4eb0c7dde91d40d6ef3f05adc1a1282d17ff1ca12470d0a008454f7ca7489", "boatstack/runtime_cache_test.go": "4cbca9dec7800d7df6e3ec0d74c7ecbe1508e5c5a288d863f35fc8d22986c308", @@ -71,10 +73,10 @@ "docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3", "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", - "docs/evidence-engineered-coding.md": "9a6e1770ed903a22ade4be804a35e5fc0a557d369b438b449cce967b761f6943", + "docs/evidence-engineered-coding.md": "2935abc292b02c6842ea17ace5af9eecd5464ea1f24a17f5d594dd2a49a1c003", "docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c", - "docs/getting-started.md": "2a642a8ef072502f1a099e211cb599a0179023d1b48b41b4d6b407881494e594", - "docs/public-claims.json": "e68306b85634d7868ca9697da1e7491fd72abe0ba3eb0706a958b35b06930afb", + "docs/getting-started.md": "61efc6bd618bd6674687d8efac9e1bc425fe5c10e72f87e267461dd2a830736e", + "docs/public-claims.json": "3311b27e29ef81aa25a1503e2407ea12331ba4c626936ea917b9545fba236830", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -88,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": "50e07d4290abd9c122a300f7f5aa443d8e829ea5010abbf8ddf0711d2497b576", + "labs/diagram-json/plan.lock.json": "4bdb23a2800a9f826a1298dfb89f63a64ac7004aa15779edeb935075a9fcaaae", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -108,13 +110,14 @@ "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-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" }, "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "569709dad427c28f7e866a1bd72ca0312d7b2f1b", + "commit": "ae167a3ce7759793d4dfbd710eea7ecbbf577058", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/SKILL.md b/boatstack/SKILL.md index c3838c7..895adba 100644 --- a/boatstack/SKILL.md +++ b/boatstack/SKILL.md @@ -13,6 +13,7 @@ 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. +- `run`: drive the verified feature through every delivery slice and PR publication, pausing at approval, product-decision, and publication boundaries. - `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. @@ -30,6 +31,12 @@ For the full state machine, read [workflow.md](references/workflow.md). For arti 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 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. + +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. + ## 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 c425719..f503959 100644 --- a/boatstack/cmd/boatstack-helper/main.go +++ b/boatstack/cmd/boatstack-helper/main.go @@ -325,6 +325,29 @@ func nextStatusCommand(arguments []string) int { return 0 } +func runPreflightCommand(arguments []string) int { + flags := flag.NewFlagSet("run-preflight", flag.ContinueOnError) + repo := flags.String("repo", ".", "repository whose Git state should be verified before boatstack run") + jsonOutput := flags.Bool("json", false, "print the versioned structured preflight") + if err := flags.Parse(arguments); err != nil { + return 2 + } + status := boatstack.CheckRunPreflight(*repo) + if *jsonOutput { + value, err := boatstack.MarshalJSON(status) + if err != nil { + return fail(err) + } + fmt.Print(string(value)) + } else { + fmt.Printf("Boatstack run preflight: %s\nReason: %s\n", status.VerificationStatus, status.Reason) + } + if status.VerificationStatus != "VERIFIED" { + return 1 + } + return 0 +} + func recordChangeCommand(arguments []string) int { flags := flag.NewFlagSet("record-change", flag.ContinueOnError) options := boatstack.ChangeObservationOptions{} @@ -512,7 +535,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] { @@ -542,6 +565,8 @@ func run() int { return deliveryStatusCommand(os.Args[2:]) case "next-status": return nextStatusCommand(os.Args[2:]) + case "run-preflight": + return runPreflightCommand(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 d495213..5800d81 100644 --- a/boatstack/export.go +++ b/boatstack/export.go @@ -40,6 +40,10 @@ var claudeVisibleSkills = []claudeSkillSpec{ Name: "boatstack-next", Description: "Report the verified Boatstack stage and exactly one next action without changing state.", }, + { + Name: "boatstack-run", + Description: "Drive the verified Boatstack feature through every delivery slice and PR publication, pausing only at required human boundaries.", + }, { Name: "auto-plan", Description: "Refine one saved Plan-mode proposal into a reviewable Boatstack feature plan.", @@ -231,6 +235,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.", + "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.", "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.", @@ -274,12 +279,12 @@ Boatstack's repository hooks deny high-confidence irreversible operations across adapterSkill := fmt.Sprintf(`--- name: %s - 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. + description: Use when the user asks what is next in Boatstack, asks Boatstack to run a feature through ship, 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. 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. + Read .product-loop/project.json and .product-loop/workflow.md. The requested operation is supplied by the user; valid operations are next, boatstack-next, run, boatstack-run, 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. Route run and requests such as "run Boatstack through ship" to boatstack-run. 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 5f07304..126b7b3 100644 --- a/boatstack/export_test.go +++ b/boatstack/export_test.go @@ -169,6 +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"}, "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"}, @@ -237,6 +238,12 @@ func TestExportAndDriftCheck(t *testing.T) { if _, exists := bundle.Files[".cursor/commands/pr-brief.md"]; exists { t.Fatal("PR brief must remain natural-language behavior, not a public command") } + run := string(bundle.Files[".cursor/commands/boatstack-run.md"]) + for _, expected := range []string{"run-preflight --repo . --json", "fetch", "next-status --repo . --json", "three complete automated repair-and-gate cycles", "automatically continue the run", "never merge or deploy"} { + if !strings.Contains(run, expected) { + t.Fatalf("boatstack-run adapter is missing %q", expected) + } + } update := string(bundle.Files[".cursor/commands/boatstack-update.md"]) for _, expected := range []string{"check-update", "chore/update-boatstack-v", "BOATSTACK_MODE=update", "Reply `o` to open update PR.", "full reply open update PR for compatibility", "Never merge"} { if !strings.Contains(update, expected) { @@ -357,7 +364,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{"boatstack-next", "auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { + for _, expected := range []string{"boatstack-next", "boatstack-run", "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) } @@ -388,7 +395,7 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { } } } - for _, operation := range []string{"next", "boatstack-next", "auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { + for _, operation := range []string{"next", "boatstack-next", "run", "boatstack-run", "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/references/workflow.md b/boatstack/references/workflow.md index a4bfd9d..d638e52 100644 --- a/boatstack/references/workflow.md +++ b/boatstack/references/workflow.md @@ -66,6 +66,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 | +| `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 | | `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 | @@ -76,6 +77,12 @@ Lead with a plain outcome, never a machine code such as `PASS`, `PLAN_APPROVED`, | `boatstack-update` current / postponed / prepared / published / blocked | **Boatstack is current** -> no action required; **Update postponed** -> finish feature work and rerun from the clean default branch; **Boatstack update ready** -> reply `o` to open the update PR; **Update PR opened** -> review the PR; **Update needs attention** -> address the one reported collision or health failure | | `retro` | **Improvement proposed** -> review or authorize the experiment | +### 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. + +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. + ### Reply shortcuts Finite input uses one global, state-scoped reply grammar: diff --git a/boatstack/run.go b/boatstack/run.go new file mode 100644 index 0000000..c4fb385 --- /dev/null +++ b/boatstack/run.go @@ -0,0 +1,132 @@ +package boatstack + +import ( + "fmt" + "path/filepath" + "strconv" + "strings" +) + +const runPreflightSchemaVersion = 1 + +var runGitCommand = gitCommand + +// RunPreflight is the deterministic Git freshness boundary used before the +// host-driven run operation is allowed to mutate workflow or product state. +type RunPreflight struct { + SchemaVersion int `json:"schema_version"` + VerificationStatus string `json:"verification_status"` + BaseBranch string `json:"base_branch,omitempty"` + HeadBranch string `json:"head_branch,omitempty"` + Upstream string `json:"upstream,omitempty"` + Relation string `json:"relation,omitempty"` + Reason string `json:"reason"` +} + +func blockedRunPreflight(base, head, upstream, relation, reason string) RunPreflight { + return RunPreflight{ + SchemaVersion: runPreflightSchemaVersion, VerificationStatus: "BLOCKED", + BaseBranch: base, HeadBranch: head, Upstream: upstream, Relation: relation, Reason: reason, + } +} + +func runBranches(repo string) (string, string, error) { + base := defaultPRBase(repo) + head, err := runGitCommand(repo, "branch", "--show-current") + if err != nil || strings.TrimSpace(head) == "" { + return "", "", fmt.Errorf("boatstack run requires a named current branch") + } + + active, err := ActiveManagedDeliveries(repo) + if err != nil { + return "", "", err + } + if len(active) > 1 { + return base, head, fmt.Errorf("more than one managed delivery is active; Boatstack run will not choose by recency") + } + if len(active) == 1 { + state, stateErr := CurrentDeliveryState(repo, active[0]) + if stateErr != nil { + return "", "", stateErr + } + slice, sliceErr := activeDeliverySlice(state) + if sliceErr != nil { + return "", "", sliceErr + } + if slice.BaseBranch != "" { + base = slice.BaseBranch + } + if slice.HeadBranch != "" && slice.HeadBranch != head { + return base, head, fmt.Errorf("active delivery slice %s requires head branch %s; current branch is %s", slice.ID, slice.HeadBranch, head) + } + } + if head == base { + return base, head, fmt.Errorf("Boatstack run requires a feature branch; current branch %s is the configured base branch", head) + } + return base, head, nil +} + +// CheckRunPreflight fetches origin and proves that the current branch contains +// the fetched base and is not behind or diverged from its configured upstream. +// It never merges, rebases, switches branches, discards changes, or pushes. +func CheckRunPreflight(repoPath string) RunPreflight { + repo, err := ResolveRepository(repoPath) + if err != nil { + return blockedRunPreflight("", "", "", "INVALID_REPOSITORY", err.Error()) + } + if !fileExists(filepath.Join(repo, ".product-loop", "project.json")) { + return blockedRunPreflight("", "", "", "NOT_INITIALIZED", "This repository has no Boatstack project installation to run.") + } + if _, err := runGitCommand(repo, "remote", "get-url", "origin"); err != nil { + return blockedRunPreflight("", "", "", "MISSING_ORIGIN", "Boatstack run requires a usable origin remote.") + } + if _, err := runGitCommand(repo, "fetch", "origin"); err != nil { + return blockedRunPreflight("", "", "", "FETCH_FAILED", "Boatstack could not fetch origin: "+err.Error()) + } + + base, head, err := runBranches(repo) + if err != nil { + return blockedRunPreflight(base, head, "", "BRANCH_MISMATCH", err.Error()) + } + remoteBase := "refs/remotes/origin/" + base + if _, err := runGitCommand(repo, "rev-parse", "--verify", remoteBase+"^{commit}"); err != nil { + return blockedRunPreflight(base, head, "", "MISSING_REMOTE_BASE", fmt.Sprintf("Fetched origin does not contain base branch %s.", base)) + } + if _, err := runGitCommand(repo, "merge-base", "--is-ancestor", remoteBase, "HEAD"); err != nil { + return blockedRunPreflight(base, head, "", "STALE_BASE", fmt.Sprintf("Current branch %s does not contain fetched origin/%s; synchronize it outside Boatstack run.", head, base)) + } + + upstream, upstreamErr := runGitCommand(repo, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}") + relation := "UNPUBLISHED" + if upstreamErr == nil && upstream != "" { + counts, countErr := runGitCommand(repo, "rev-list", "--left-right", "--count", "HEAD...@{upstream}") + if countErr != nil { + return blockedRunPreflight(base, head, upstream, "UPSTREAM_UNKNOWN", "Boatstack could not compare the current branch with its upstream: "+countErr.Error()) + } + fields := strings.Fields(counts) + if len(fields) != 2 { + return blockedRunPreflight(base, head, upstream, "UPSTREAM_UNKNOWN", "Boatstack received an invalid Git upstream comparison.") + } + ahead, aheadErr := strconv.Atoi(fields[0]) + behind, behindErr := strconv.Atoi(fields[1]) + if aheadErr != nil || behindErr != nil { + return blockedRunPreflight(base, head, upstream, "UPSTREAM_UNKNOWN", "Boatstack received an invalid Git upstream comparison.") + } + switch { + case ahead > 0 && behind > 0: + return blockedRunPreflight(base, head, upstream, "DIVERGED", fmt.Sprintf("Current branch %s has diverged from %s; synchronize it outside Boatstack run.", head, upstream)) + case behind > 0: + return blockedRunPreflight(base, head, upstream, "BEHIND", fmt.Sprintf("Current branch %s is behind %s; synchronize it outside Boatstack run.", head, upstream)) + case ahead > 0: + relation = "AHEAD" + default: + relation = "CURRENT" + } + } + + return RunPreflight{ + SchemaVersion: runPreflightSchemaVersion, VerificationStatus: "VERIFIED", + BaseBranch: base, HeadBranch: head, Upstream: upstream, Relation: relation, + Reason: "Origin was fetched and the current branch is fresh enough to run Boatstack.", + } +} diff --git a/boatstack/run_test.go b/boatstack/run_test.go new file mode 100644 index 0000000..9b56b15 --- /dev/null +++ b/boatstack/run_test.go @@ -0,0 +1,216 @@ +package boatstack + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" +) + +func runTestRepo(t *testing.T) string { + t.Helper() + repo := t.TempDir() + for _, args := range [][]string{ + {"init", "-b", "main"}, + {"config", "user.name", "Boatstack Test"}, + {"config", "user.email", "boatstack@example.test"}, + } { + if output, err := exec.Command("git", append([]string{"-C", repo}, args...)...).CombinedOutput(); err != nil { + t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, output) + } + } + if err := os.WriteFile(filepath.Join(repo, "README.md"), []byte("test\n"), 0o644); err != nil { + t.Fatal(err) + } + for _, args := range [][]string{{"add", "README.md"}, {"commit", "-m", "initial"}} { + if output, err := exec.Command("git", append([]string{"-C", repo}, args...)...).CombinedOutput(); err != nil { + t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, output) + } + } + configDirectory := filepath.Join(repo, ".product-loop") + if err := os.MkdirAll(configDirectory, 0o755); err != nil { + t.Fatal(err) + } + config := `{"schema_version":1,"project":{"name":"test","default_branch":"main","commands":{"test":"go test ./..."}},"workflow":{"human_plan_approval":true,"independent_review_for_high_risk":true,"allow_pass_with_gaps":false},"adapters":["cursor"]}` + if err := os.WriteFile(filepath.Join(configDirectory, "project.json"), []byte(config), 0o644); err != nil { + t.Fatal(err) + } + return repo +} + +func withRunGit(t *testing.T, responses map[string]struct { + value string + err error +}) { + t.Helper() + old := runGitCommand + runGitCommand = func(_ string, arguments ...string) (string, error) { + key := strings.Join(arguments, " ") + if response, ok := responses[key]; ok { + return response.value, response.err + } + return "", fmt.Errorf("unexpected git command: %s", key) + } + t.Cleanup(func() { runGitCommand = old }) +} + +func TestCheckRunPreflightRequiresOriginBeforeMutation(t *testing.T) { + repo := runTestRepo(t) + before, err := os.ReadFile(filepath.Join(repo, ".product-loop", "project.json")) + if err != nil { + t.Fatal(err) + } + status := CheckRunPreflight(repo) + after, err := os.ReadFile(filepath.Join(repo, ".product-loop", "project.json")) + if err != nil { + t.Fatal(err) + } + if status.VerificationStatus != "BLOCKED" || status.Relation != "MISSING_ORIGIN" { + t.Fatalf("unexpected preflight: %+v", status) + } + if !reflect.DeepEqual(before, after) { + t.Fatal("blocked preflight changed Boatstack state") + } +} + +func TestCheckRunPreflightFetchesAndAcceptsFreshUnpublishedBranch(t *testing.T) { + repo := runTestRepo(t) + remote := filepath.Join(t.TempDir(), "origin.git") + if output, err := exec.Command("git", "init", "--bare", remote).CombinedOutput(); err != nil { + t.Fatalf("git init --bare: %v: %s", err, output) + } + for _, args := range [][]string{ + {"remote", "add", "origin", remote}, + {"push", "-u", "origin", "main"}, + {"switch", "-c", "feature"}, + } { + if output, err := exec.Command("git", append([]string{"-C", repo}, args...)...).CombinedOutput(); err != nil { + t.Fatalf("git %s: %v: %s", strings.Join(args, " "), err, output) + } + } + status := CheckRunPreflight(repo) + if status.VerificationStatus != "VERIFIED" || status.Relation != "UNPUBLISHED" || status.BaseBranch != "main" || status.HeadBranch != "feature" { + t.Fatalf("unexpected preflight: %+v", status) + } +} + +func TestCheckRunPreflightBlocksFetchAndFreshnessFailures(t *testing.T) { + tests := []struct { + name string + responses map[string]struct { + value string + err error + } + relation string + }{ + { + name: "fetch failure", + responses: map[string]struct { + value string + err error + }{ + "remote get-url origin": {value: "git@example.test/repo.git"}, + "fetch origin": {err: fmt.Errorf("authentication failed")}, + }, + relation: "FETCH_FAILED", + }, + { + name: "stale base", + responses: map[string]struct { + value string + err error + }{ + "remote get-url origin": {value: "git@example.test/repo.git"}, + "fetch origin": {}, + "branch --show-current": {value: "feature"}, + "rev-parse --verify refs/remotes/origin/main^{commit}": {value: "abc"}, + "merge-base --is-ancestor refs/remotes/origin/main HEAD": {err: fmt.Errorf("not ancestor")}, + }, + relation: "STALE_BASE", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + repo := runTestRepo(t) + withRunGit(t, test.responses) + status := CheckRunPreflight(repo) + if status.VerificationStatus != "BLOCKED" || status.Relation != test.relation { + t.Fatalf("unexpected preflight: %+v", status) + } + }) + } +} + +func TestCheckRunPreflightClassifiesUpstreamRelations(t *testing.T) { + for _, test := range []struct { + counts, relation, verification string + }{ + {counts: "0 0", relation: "CURRENT", verification: "VERIFIED"}, + {counts: "2 0", relation: "AHEAD", verification: "VERIFIED"}, + {counts: "0 1", relation: "BEHIND", verification: "BLOCKED"}, + {counts: "2 1", relation: "DIVERGED", verification: "BLOCKED"}, + } { + t.Run(test.relation, func(t *testing.T) { + repo := runTestRepo(t) + withRunGit(t, map[string]struct { + value string + err error + }{ + "remote get-url origin": {value: "git@example.test/repo.git"}, + "fetch origin": {}, + "branch --show-current": {value: "feature"}, + "rev-parse --verify refs/remotes/origin/main^{commit}": {value: "abc"}, + "merge-base --is-ancestor refs/remotes/origin/main HEAD": {}, + "rev-parse --abbrev-ref --symbolic-full-name @{upstream}": {value: "origin/feature"}, + "rev-list --left-right --count HEAD...@{upstream}": {value: test.counts}, + }) + status := CheckRunPreflight(repo) + if status.VerificationStatus != test.verification || status.Relation != test.relation { + t.Fatalf("unexpected preflight: %+v", status) + } + }) + } +} + +func TestCheckRunPreflightBlocksConstrainedDeliveryBranchMismatch(t *testing.T) { + repo := runTestRepo(t) + directory := filepath.Join(repo, ".product-loop", "features", "bounded-run") + 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) + } + lockHash, err := SHA256File(lockPath) + if err != nil { + t.Fatal(err) + } + if err := saveDeliveryState(repo, DeliveryState{ + SchemaVersion: deliveryStateSchemaVersion, + Feature: "bounded-run", + PlanLockHash: lockHash, + ActiveIndex: 0, + Slices: []DeliverySlice{{ + ID: "delivery", Title: "Delivery", Status: "BUILD", + BaseBranch: "main", HeadBranch: "expected-feature", + }}, + }); err != nil { + t.Fatal(err) + } + withRunGit(t, map[string]struct { + value string + err error + }{ + "remote get-url origin": {value: "git@example.test/repo.git"}, + "fetch origin": {}, + "branch --show-current": {value: "wrong-feature"}, + }) + status := CheckRunPreflight(repo) + if status.VerificationStatus != "BLOCKED" || status.Relation != "BRANCH_MISMATCH" || !strings.Contains(status.Reason, "expected-feature") { + t.Fatalf("unexpected preflight: %+v", status) + } +} diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index dd2b864..51d065f 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 **10016 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 **10474 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 [`569709dad427c28f7e866a1bd72ca0312d7b2f1b`](https://github.com/operatorstack/intelligence-flow/tree/569709dad427c28f7e866a1bd72ca0312d7b2f1b/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 [`ae167a3ce7759793d4dfbd710eea7ecbbf577058`](https://github.com/operatorstack/intelligence-flow/tree/ae167a3ce7759793d4dfbd710eea7ecbbf577058/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 f026d79..c86dea3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -18,6 +18,8 @@ Ask Boatstack for the next verified stage without changing anything: 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**. +For a small verified feature, `/boatstack-run` in Cursor or Claude Code and `$boatstack run` in Codex drive every declared delivery slice through PR publication. The command fetches `origin` before mutation and pauses for required plan approval, product decisions, and the exact PR open/update confirmation. Merge and deploy remain separate. + ## 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 50e8e05..b62db03 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "569709dad427c28f7e866a1bd72ca0312d7b2f1b", + "source_commit": "ae167a3ce7759793d4dfbd710eea7ecbbf577058", "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" }, { "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:569709dad427c28f7e866a1bd72ca0312d7b2f1b" + "last_verified_version": "source:ae167a3ce7759793d4dfbd710eea7ecbbf577058" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index 313b323..5f9ad5c 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": "569709dad427c28f7e866a1bd72ca0312d7b2f1b", + "source_commit": "ae167a3ce7759793d4dfbd710eea7ecbbf577058", "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-run-through-ship.md b/release-notes/2026-07-18-run-through-ship.md new file mode 100644 index 0000000..02786ce --- /dev/null +++ b/release-notes/2026-07-18-run-through-ship.md @@ -0,0 +1,3 @@ +### Run a verified feature through ship + +Run `$boatstack run` in Codex or `/boatstack-run` in Cursor and Claude Code to drive a small verified feature through every declared delivery slice. Boatstack fetches `origin` and blocks on stale or diverged Git state before mutation, reuses the existing build and evidence gates, retries up to three same-intent repair cycles per slice, and pauses for plan approval, product decisions, and exact PR publication confirmation. It opens or updates reviewer-ready PRs but never merges or deploys.