From 4227afebd3f66ae6726f3cee71a5877249c20cbb Mon Sep 17 00:00:00 2001 From: "boatstack-automation[bot]" Date: Sat, 18 Jul 2026 16:57:30 +0000 Subject: [PATCH] Sync Boatstack from Intelligence Flow Labs @ 9668feb5b9ba --- CONTRIBUTING.md | 2 +- README.md | 10 +- UPSTREAM.json | 29 ++--- boatstack/export.go | 80 ++++++++++++- boatstack/export_test.go | 108 +++++++++++++----- boatstack/init.go | 6 +- boatstack/init_test.go | 13 ++- boatstack/planning_test.go | 10 ++ docs/evidence-engineered-coding.md | 2 +- docs/generated-files.md | 2 +- docs/getting-started.md | 14 ++- docs/public-claims.json | 24 ++-- docs/troubleshooting.md | 11 ++ labs/diagram-json/plan.lock.json | 2 +- .../2026-07-18-claude-command-discovery.md | 3 + 15 files changed, 249 insertions(+), 67 deletions(-) create mode 100644 release-notes/2026-07-18-claude-command-discovery.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c891b72..e1d32c4 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/9749f1b5397c73f6ea9077719a85f60ae7eee1d6/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/9668feb5b9ba3b816d470dde2cc87c70811a7f9a/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 e7d72ca..01b7598 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,17 @@ Install Boatstack in its own infrastructure PR and merge it before starting a fe ## Start with two moves 1. Create and save a plan in your coding tool's Plan mode. -2. Run `/auto-plan`. +2. Start Boatstack with the entry point for your host. + +| Host | Start command | +|---|---| +| Claude Code | `/auto-plan` | +| Cursor | `/auto-plan` | +| Codex | `$boatstack auto-plan` | 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 are ready, that guidance moves through `/plan-gate` → `/build` → `/test-gate` → `/review-gate` → `/ship-gate`. +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 a9bab53..e3cf36b 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,8 +12,8 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "e1a9752e2aa9f716579096a2ed8f1d0025673ad7775a7eb3a106dc238285420a", - "README.md": "83ea436685782c5c2d2d375ceae21cb95c72a5f6608eb8187a5efc817350e081", + "CONTRIBUTING.md": "d79729327266363a2fbc47cca778245c619d874a74c7c4ae7ff6ecee1f9ba6b2", + "README.md": "d52c9be1a91165e3bb22318e3014278eb5326d10a45da6311f677d704e5c086d", "assets/boatstack-journey.svg": "c1f7fe2741f5e9ca66bb3fe9b103e6364ba5acbca8b7a8054768ffd85cf325ea", "assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4", "assets/boatstack-portability.svg": "ce648f5581d16586d25824d3a8132ef1b3d88b73329179173120129d4f74fd24", @@ -34,18 +34,18 @@ "boatstack/cmd/boatstack-helper/main.go": "dbb8cb4bb403aa36a47bd9317e9a34845cabfa5b8e4152a7fb57ded1591d8957", "boatstack/delivery.go": "bfdce7dd3bc1357a614bd458f2f6b4b8570015c117d1f7638c7a1bf3110e1a48", "boatstack/delivery_test.go": "a8a5a7e6e8dcfee1538367d49c76c531e04211876c1685265884cff26ae04497", - "boatstack/export.go": "faf905a8d45a73a03792fb000e56cba351f86caf3b3c261ff8d324e612e48377", - "boatstack/export_test.go": "9961313036b8ca8681d804193921357f29d6acd30a9532aa522986d48a9894ef", + "boatstack/export.go": "5a2002ba99b1b0c22f503f378072902393edb9d70dba06a090fe25e5ac734612", + "boatstack/export_test.go": "1cbed87e24f1b81d20fbdd64eaef0b5b713abda06518b144d5aed5726247d183", "boatstack/go.mod": "57c377eccea51372d6664de4169e2ca45806b046f7e8a98a1e35a9eb454b4b8d", "boatstack/hooks.go": "718161de45165e450369577e5ef1e9fdc76a484d61fa56a36eb6a117b2e63249", "boatstack/hooks_test.go": "c3f359416ea53f258d8747d0247381e8946efd4d4a5bcf072c4147f885475ad3", - "boatstack/init.go": "ddee0cfd8ab9f3416fb895afd99d1682ca09550d2d9b2ad3cd11f5e06438c585", - "boatstack/init_test.go": "b761ada1f5a04c0a27225a6f1eb99baf5477a424c5a9748a3267f07ba5a84605", + "boatstack/init.go": "a9740a401bf4b0b5a700b02fe8f15d80757ed2c697fa6ebce38d3c03f6c7ba75", + "boatstack/init_test.go": "ea5898df01d4fa5e039749df83f9190fd8fe95b2fe9b7e2526e0d0ecba8a7f03", "boatstack/integrations.go": "75b39ce2e662fccd66bf4b9bff0e097a4db558f23b3aa1d9bc83a5fc6373444c", "boatstack/plan.go": "d83037a9f06f927427a59ea936bbee4041ea9276dd84992599c16ef6259b2d4f", "boatstack/plan_test.go": "006cdc6681f77e579c5a0f709e30ede759c337132d4f2f5193b7b79b29bd7149", "boatstack/planning.go": "3a26417a295e5dfc2b6dcac702287c04b6053e7b74215858a4ea11cf9f9dadfe", - "boatstack/planning_test.go": "4662908c1ec063aa8ef6f91db52247864303d9b91ef2363a8f68b41082fe383f", + "boatstack/planning_test.go": "6b156a64182ed76d4c3d392b4c5a26abe5d8b81cea27ee12ac7c4627c827e186", "boatstack/pr.go": "2af70c81108288cd9cd319f7100d1abd957e7e84f8db1474afa51a8b74e9d442", "boatstack/pr_test.go": "74afb1a9be3c4a95a426515be415149ae24b077454b486d8e69b7118c42f2916", "boatstack/references/artifacts.md": "3a87b8f8d835329191bedb20c94e6056fe2251b3e9d510656909eb9d276069dc", @@ -68,14 +68,14 @@ "docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3", "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", - "docs/evidence-engineered-coding.md": "2368d5fba09ad6f24f8317fcc40f6d2208c01d1dc6d4a673d0f2477f5cca6757", - "docs/generated-files.md": "7b2e8c10a35aa351fb87753492ed3cadb05011002d2fd6ffeb1951c356f6b286", - "docs/getting-started.md": "6d98555b9d7a27091169a6a8c1efb64c84791c73a72f814e2a4dbdc149ac58a6", - "docs/public-claims.json": "b9080efb0c1d995aef961345e7dc2343cc7c83715a822af5d0ed89bca979227a", + "docs/evidence-engineered-coding.md": "027e20dc9faad84dfa533f365514ad70195cad945bf46ca17a67e3d2dcbaedbb", + "docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c", + "docs/getting-started.md": "4ef77719a01a2dbe98c5154d58fc03f51797ae0ef39a09d8804fe0708b8cc920", + "docs/public-claims.json": "915db0f39b14208ea083f9567dcedc5d19b973577c5c448136d6ca7baee94623", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", - "docs/troubleshooting.md": "a3314d0eb97643534415f3c19f1763fd80ff6bb5e98c53b646e447e0b31c4829", + "docs/troubleshooting.md": "5233172deea20a962a3557e96f864113c3f6242a835e207e108817f8051ff237", "docs/validation-and-evidence.md": "e7d91ad49c6adb44784ebe7d94feceb6abd445857f9a0716f0758bf6b55296c5", "docs/why-these-steps.md": "80957af13979070e8b2f2a8db78ce06d20d152bbc8ec41c3a8003f28393f6369", "install.ps1": "960b2b20b406bb2878a560e9ace53fe7226bc510be6ee8466ce4e608beb5625a", @@ -85,7 +85,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": "86398343fb6a7cc35a97099753ec3118ee03985b3869fd225a13b18c6b16d86c", + "labs/diagram-json/plan.lock.json": "d3b39300d7dba9b5b863826b7dc14b582963daa123fa8dda605a353043d2ad7b", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -97,6 +97,7 @@ "release-notes/2026-07-17-visible-release-messages.md": "c93e8c812528a983502263e35d66c86a265d6ccba8203f393788c069b3fa6606", "release-notes/2026-07-18-automated-releases.md": "6571eec442a27bd1a55667567f0659993c516171ef10ae71046cce80f7fd29fa", "release-notes/2026-07-18-base-aware-release-preflight.md": "cdface46ccd959a5299de4c363c9e4057e7257820dab77dcf0e587daef5d3d99", + "release-notes/2026-07-18-claude-command-discovery.md": "bb42c23041916dee21e502449143620a8dcbd0dec4aad01162293499547f1114", "release-notes/2026-07-18-global-reply-shortcuts.md": "329d6fd104079bc5f66e7c3d477f4ff2a6bb264d429485c6f42f9c203d17fa29", "release-notes/2026-07-18-harbor-lab-namespace.md": "6419c049e5a3024c5a8604e4d9fb241c27ceb80bf1d4cc62f66d1a0eaf09ea21", "release-notes/2026-07-18-host-hook-migrations.md": "1c9f81d9318854214f72802045e8e39c9ca45435af0f9d2c28fcf4ff4c1e0071", @@ -107,7 +108,7 @@ "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "9749f1b5397c73f6ea9077719a85f60ae7eee1d6", + "commit": "9668feb5b9ba3b816d470dde2cc87c70811a7f9a", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/export.go b/boatstack/export.go index f7ea34a..2498767 100644 --- a/boatstack/export.go +++ b/boatstack/export.go @@ -24,6 +24,44 @@ type ExportBundle struct { Config ProjectConfig } +type claudeSkillSpec struct { + Name string + Description string + ArgumentHint string +} + +var claudeVisibleSkills = []claudeSkillSpec{ + { + Name: "auto-plan", + Description: "Refine one saved Plan-mode proposal into a reviewable Boatstack feature plan.", + ArgumentHint: "[plan-file]", + }, + { + Name: "plan-gate", + Description: "Review and explicitly approve a Boatstack feature plan before implementation.", + }, + { + Name: "build", + Description: "Implement the currently approved Boatstack delivery slice.", + }, + { + Name: "test-gate", + Description: "Validate the active Boatstack delivery slice and record current test evidence.", + }, + { + Name: "review-gate", + Description: "Review the active Boatstack delivery slice against approved intent and evidence.", + }, + { + Name: "ship-gate", + Description: "Prepare and, after confirmation, publish the active Boatstack delivery slice as a pull request.", + }, + { + Name: "boatstack-update", + Description: "Prepare a separate reviewed update of Boatstack's repository infrastructure.", + }, +} + func LoadConfig(path string) (ProjectConfig, []byte, error) { raw, err := os.ReadFile(path) if err != nil { @@ -95,6 +133,23 @@ Follow the User-facing response contract in @.product-loop/workflow.md. Lead wit `, operation, operation, preflight, extra) } +func claudeOperationSkill(spec claudeSkillSpec, operationBody string) string { + argumentHint := "" + arguments := "" + if spec.ArgumentHint != "" { + argumentHint = fmt.Sprintf("argument-hint: %q\n", spec.ArgumentHint) + arguments = "\n\nUser arguments: $ARGUMENTS" + } + return fmt.Sprintf(`--- +name: %s +description: %s +%sdisable-model-invocation: true +--- + +%s%s +`, spec.Name, spec.Description, argumentHint, strings.TrimSpace(operationBody), arguments) +} + func BuildExportBundle(configPath string, config ProjectConfig, rawConfig []byte, adapterName string) (ExportBundle, error) { if !adapterNamePattern.MatchString(adapterName) { return ExportBundle{}, fmt.Errorf("adapter name must be a lowercase kebab-case slug") @@ -202,7 +257,7 @@ Boatstack's repository hooks deny high-confidence irreversible operations across adapterSkill := fmt.Sprintf(`--- name: %s -description: Run Boatstack's evidence-engineered coding node for question-led planning, explicit approval, open implementation, evidence gates, and PR preparation. +description: Use when the user asks Boatstack to auto-plan, approve a plan, build, test, review, ship, update Boatstack, or run a retrospective. --- # Boatstack adapter @@ -232,12 +287,31 @@ When the user asks to update Boatstack, run the boatstack-update operation. Neve For a managed ship, use the internal pr-context operation with --feature to project the feature spec, accepted decisions, actual committed diff, evidence ledger, review findings, gaps, rollout, and rollback into the required pr.md artifact. Inspect the returned changed files, diff stat, high-risk matches, and the actual diff before writing claims; commits alone are not authoritative. Always include why, what changed, review order, evidence, gaps/risks, rollout/rollback, and collapsed provenance. Add UI evidence, security/privacy, migration, or operations sections only when relevant. For a natural-language request to improve an existing or ad-hoc PR, run pr-context without --feature and use the same reviewer-first format from observed branch facts, but mark unavailable approval or gate evidence as NOT_VERIFIED. Never create or advertise a /pr-brief command. Validate with check-pr and always show the exact title and rendered body before publication. Ask for state-scoped o to open or u to update the PR. Only after the matching shortcut or compatible full reply, commit only pr.md, revalidate the unchanged preview fingerprint, and invoke the internal publish-pr operation with the selected action. It may perform a normal push but never force-push. Any intervening product diff or evidence change invalidates the preview. Keep model attribution inside collapsed provenance. Internal helper names and hashes stay out of the primary response. If gstack is enabled, use only its namespaced /gstack-* specialist lenses inside Boatstack operations. If Spec Kit is enabled, use it to generate or cross-check artifacts; never invoke speckit.implement to bypass Boatstack's plan approval and build gate. -`, adapterName) + `, adapterName) if contains(adapters, "claude") { - files[fmt.Sprintf(".claude/skills/%s/SKILL.md", adapterName)], err = GeneratedFrontmatter(adapterSkill) + claudeAdapterSkill := strings.Replace( + adapterSkill, + "\n---\n\n# Boatstack adapter", + "\nuser-invocable: false\n---\n\n# Boatstack adapter", + 1, + ) + files[fmt.Sprintf(".claude/skills/%s/SKILL.md", adapterName)], err = GeneratedFrontmatter(claudeAdapterSkill) if err != nil { return ExportBundle{}, err } + for _, spec := range claudeVisibleSkills { + extra, ok := operations[spec.Name] + if !ok { + return ExportBundle{}, fmt.Errorf("missing operation instructions for Claude skill %s", spec.Name) + } + path := fmt.Sprintf(".claude/skills/%s/SKILL.md", spec.Name) + files[path], err = GeneratedFrontmatter( + claudeOperationSkill(spec, commandBody(spec.Name, extra)), + ) + if err != nil { + return ExportBundle{}, err + } + } } if contains(adapters, "codex") { files[fmt.Sprintf(".agents/skills/%s/SKILL.md", adapterName)], err = GeneratedFrontmatter(adapterSkill) diff --git a/boatstack/export_test.go b/boatstack/export_test.go index 357df69..5d6e4de 100644 --- a/boatstack/export_test.go +++ b/boatstack/export_test.go @@ -50,6 +50,8 @@ func TestExportAndDriftCheck(t *testing.T) { ".cursor/commands/plan-gate.md", ".cursor/commands/review.md", ".claude/skills/boatstack/SKILL.md", + ".claude/skills/auto-plan/SKILL.md", + ".claude/skills/boatstack-update/SKILL.md", ".agents/skills/boatstack/SKILL.md", ".product-loop/.gitignore", ".product-loop/templates/plan.md", @@ -62,6 +64,47 @@ func TestExportAndDriftCheck(t *testing.T) { t.Fatalf("expected generated file %s", path) } } + claudeSkillPaths := map[string]bool{} + for path := range bundle.Files { + if strings.HasPrefix(path, ".claude/skills/") && strings.HasSuffix(path, "/SKILL.md") { + claudeSkillPaths[path] = true + } + } + if len(claudeSkillPaths) != len(claudeVisibleSkills)+1 { + t.Fatalf("generated %d Claude skills, want %d: %#v", len(claudeSkillPaths), len(claudeVisibleSkills)+1, claudeSkillPaths) + } + for _, spec := range claudeVisibleSkills { + path := ".claude/skills/" + spec.Name + "/SKILL.md" + skill := string(bundle.Files[path]) + for _, expected := range []string{ + "name: " + spec.Name, + "description: " + spec.Description, + "disable-model-invocation: true", + "Run the " + spec.Name + " operation", + ".product-loop/workflow.md", + "User-facing response contract", + } { + if !strings.Contains(skill, expected) { + t.Fatalf("%s is missing %q", path, expected) + } + } + } + claudeAutoPlan := string(bundle.Files[".claude/skills/auto-plan/SKILL.md"]) + for _, expected := range []string{`argument-hint: "[plan-file]"`, "$ARGUMENTS", "/auto-plan "} { + if !strings.Contains(claudeAutoPlan, expected) { + t.Fatalf("Claude auto-plan skill is missing argument behavior %q", expected) + } + } + claudeRouter := string(bundle.Files[".claude/skills/boatstack/SKILL.md"]) + if !strings.Contains(claudeRouter, "user-invocable: false") || strings.Contains(claudeRouter, "disable-model-invocation: true") { + t.Fatal("Claude Boatstack router must be hidden from users but available to the model") + } + for _, operation := range []string{"retro", "review", "ship"} { + path := ".claude/skills/" + operation + "/SKILL.md" + if claudeSkillPaths[path] { + t.Fatalf("internal or alias operation must not be a visible Claude skill: %s", path) + } + } if _, exists := bundle.Files[".product-loop/tools/approve_plan.py"]; exists { t.Fatal("public export must not contain Python runtime tools") } @@ -242,7 +285,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", "retro"} { + for _, expected := range []string{"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) } @@ -250,6 +293,11 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { t.Fatalf("Cursor does not expose portable operation %q", expected) } } + for _, spec := range claudeVisibleSkills { + if _, exists := bundle.Files[".claude/skills/"+spec.Name+"/SKILL.md"]; !exists { + t.Fatalf("Claude does not expose user operation %q", spec.Name) + } + } for _, expected := range []string{"source plan", "plan.md", "approval.md", "evidence", "gaps", "review", "pr.md"} { if !strings.Contains(strings.ToLower(artifacts), strings.ToLower(expected)) { t.Fatalf("repository artifact contract is missing %q", expected) @@ -268,36 +316,44 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { } } } - for _, host := range []string{"claude", "codex"} { - for _, operation := range []string{"auto-plan", "plan-gate", "build", "test-gate", "review-gate", "ship-gate", "boatstack-update", "retro"} { - if !strings.Contains(hostSurfaces[host], operation) { - t.Fatalf("%s adapter does not expose portable operation %q", host, operation) - } + for _, operation := range []string{"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) + } + if !strings.Contains(hostSurfaces["claude"], operation) { + t.Fatalf("Claude natural-language router does not declare portable operation %q", operation) } } } func TestExportRefusesUserOwnedCollision(t *testing.T) { - repo := t.TempDir() - path := filepath.Join(repo, ".cursor", "rules", "boatstack.mdc") - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte("user owned\n"), 0o644); err != nil { - t.Fatal(err) - } - config := testConfig() - raw, _ := MarshalJSON(config) - bundle, err := BuildExportBundle("config.json", config, raw, "boatstack") - if err != nil { - t.Fatal(err) - } - if err := WriteExport(repo, bundle.Files); err == nil || !strings.Contains(err.Error(), "user-owned") { - t.Fatalf("expected user-owned collision, got %v", err) - } - value, _ := os.ReadFile(path) - if string(value) != "user owned\n" { - t.Fatal("collision handling modified the user-owned file") + for _, relative := range []string{ + ".cursor/rules/boatstack.mdc", + ".claude/skills/auto-plan/SKILL.md", + } { + t.Run(relative, func(t *testing.T) { + repo := t.TempDir() + path := filepath.Join(repo, filepath.FromSlash(relative)) + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte("user owned\n"), 0o644); err != nil { + t.Fatal(err) + } + config := testConfig() + raw, _ := MarshalJSON(config) + bundle, err := BuildExportBundle("config.json", config, raw, "boatstack") + if err != nil { + t.Fatal(err) + } + if err := WriteExport(repo, bundle.Files); err == nil || !strings.Contains(err.Error(), "user-owned") || !strings.Contains(err.Error(), relative) { + t.Fatalf("expected named user-owned collision, got %v", err) + } + value, _ := os.ReadFile(path) + if string(value) != "user owned\n" { + t.Fatal("collision handling modified the user-owned file") + } + }) } } diff --git a/boatstack/init.go b/boatstack/init.go index 2282660..54efdd9 100644 --- a/boatstack/init.go +++ b/boatstack/init.go @@ -498,8 +498,12 @@ func RunInit(options InitOptions) error { } else { fmt.Fprintln(options.Output, "\nAfter that PR is merged, reload Cursor, Codex, or Claude and start in Plan mode:") fmt.Fprintln(options.Output, " 1. Describe the product change and save the host plan (use .product-loop/intake/ if the host exposes no path).") - fmt.Fprintln(options.Output, " 2. Run /auto-plan") } + fmt.Fprintln(options.Output, "Boatstack start command by host:") + 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, "If Boatstack created .claude/skills during an active Claude Code session, reload Claude Code before using its slash commands.") return nil } diff --git a/boatstack/init_test.go b/boatstack/init_test.go index 922bcd8..e1d523f 100644 --- a/boatstack/init_test.go +++ b/boatstack/init_test.go @@ -27,7 +27,7 @@ func TestRuntimeFreeInit(t *testing.T) { } for _, path := range []string{ ".boatstack-project.json", ".product-loop/project.json", ".product-loop/generated.lock.json", - ".product-loop/bin/install.lock.json", ".cursor/commands/auto-plan.md", ".product-loop/hooks/guard.sh", + ".product-loop/bin/install.lock.json", ".cursor/commands/auto-plan.md", ".claude/skills/auto-plan/SKILL.md", ".product-loop/hooks/guard.sh", ".cursor/hooks.json", ".claude/settings.json", ".codex/hooks.json", } { if !fileExists(filepath.Join(repo, filepath.FromSlash(path))) { @@ -54,6 +54,17 @@ func TestRuntimeFreeInit(t *testing.T) { t.Fatalf("init output is missing %q: %s", expected, output.String()) } } + for _, expected := range []string{ + "Boatstack start command by host:", + "Claude Code: /auto-plan", + "Cursor: /auto-plan", + "Codex: $boatstack auto-plan", + "reload Claude Code before using its slash commands", + } { + if !strings.Contains(output.String(), expected) { + t.Fatalf("init output is missing host start guidance %q: %s", expected, output.String()) + } + } configValue, _ := os.ReadFile(filepath.Join(repo, ".boatstack-project.json")) if strings.Contains(string(configValue), `"status"`) { t.Fatal("machine-local integration status leaked into repository configuration") diff --git a/boatstack/planning_test.go b/boatstack/planning_test.go index b41f2df..fb05a74 100644 --- a/boatstack/planning_test.go +++ b/boatstack/planning_test.go @@ -146,6 +146,16 @@ func TestDoctorDetectsMissingConfigAdapterAndVersionDrift(t *testing.T) { if err := WriteExport(repo, bundle.Files); err != nil { t.Fatal(err) } + claudeSkill := filepath.Join(repo, ".claude", "skills", "auto-plan", "SKILL.md") + if err := os.Remove(claudeSkill); err != nil { + t.Fatal(err) + } + if err := Doctor(repo); err == nil || !strings.Contains(err.Error(), "missing .claude/skills/auto-plan/SKILL.md") { + t.Fatalf("expected missing Claude skill diagnosis, got %v", err) + } + if err := WriteExport(repo, bundle.Files); err != nil { + t.Fatal(err) + } lockPath := filepath.Join(repo, ".product-loop", "bin", "install.lock.json") lockValue, _ := os.ReadFile(lockPath) var lock map[string]any diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index 7b6cc9f..dade05a 100644 --- a/docs/evidence-engineered-coding.md +++ b/docs/evidence-engineered-coding.md @@ -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 [`9749f1b5397c73f6ea9077719a85f60ae7eee1d6`](https://github.com/operatorstack/intelligence-flow/tree/9749f1b5397c73f6ea9077719a85f60ae7eee1d6/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 [`9668feb5b9ba3b816d470dde2cc87c70811a7f9a`](https://github.com/operatorstack/intelligence-flow/tree/9668feb5b9ba3b816d470dde2cc87c70811a7f9a/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/generated-files.md b/docs/generated-files.md index bdbe1e8..8d49d5b 100644 --- a/docs/generated-files.md +++ b/docs/generated-files.md @@ -13,7 +13,7 @@ Boatstack creates installation state once and feature evidence repeatedly. Keepi |---|---|---| | `.boatstack-project.json` | Project-owned repository facts and commands | Review and edit | | `.product-loop/` references, templates, hooks, and generated lock | Shared Boatstack runtime | Commit; regenerate rather than hand-edit | -| `.cursor/`, `.agents/`, and `.claude/` Boatstack adapters | Portable host commands and skills | Commit | +| `.cursor/`, `.agents/`, and `.claude/` Boatstack adapters | Cursor commands, the Codex router, and Claude's visible workflow skills plus hidden natural-language router | Commit | | `.github/PULL_REQUEST_TEMPLATE/boatstack.md` | Fallback PR structure | Commit | | `.cursor/hooks.json`, `.claude/settings.json`, `.codex/hooks.json` | Boatstack fragments merged with existing host settings | Review and commit | | `.product-loop/bin/` | Verified worktree-local helper and install lock | Never commit; it is ignored and hydrates automatically | diff --git a/docs/getting-started.md b/docs/getting-started.md index e662ae6..640cea1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -49,11 +49,17 @@ Add account recovery without removing the existing passwordless sign-in flow. Let the host inspect the relevant repository slice and save its plan. Boatstack uses a host-exposed path when available; otherwise save exactly one non-empty plan under `.product-loop/intake/`. -Run: +Start Boatstack with the entry point for your host: -```text -/auto-plan -``` +| Host | Start command | +|---|---| +| Claude Code | `/auto-plan` | +| Cursor | `/auto-plan` | +| Codex | `$boatstack auto-plan` | + +The examples below use the Claude Code and Cursor slash-command form. In Codex, use the same operation name after `$boatstack`, such as `$boatstack plan-gate`. + +If Boatstack was installed while Claude Code was already open and the project did not previously have `.claude/skills/`, reload Claude Code once so it can discover the new slash commands. Boatstack can discover repository facts. It cannot choose product behavior for you. When different answers would materially change the feature, it asks in plain language and waits for your answer. diff --git a/docs/public-claims.json b/docs/public-claims.json index e301e90..6f111f8 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "9749f1b5397c73f6ea9077719a85f60ae7eee1d6", + "source_commit": "9668feb5b9ba3b816d470dde2cc87c70811a7f9a", "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" }, { "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:9749f1b5397c73f6ea9077719a85f60ae7eee1d6" + "last_verified_version": "source:9668feb5b9ba3b816d470dde2cc87c70811a7f9a" } ] } diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 3b93029..f317161 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -34,6 +34,17 @@ ls .cursor/commands Rerun the installer and reload Cursor when files are missing. Commit the restored adapter in a dedicated infrastructure PR. +## Claude Code cannot find a slash command + +Claude Code reads Boatstack's user-facing workflow skills from `.claude/skills//SKILL.md`. The central `.claude/skills/boatstack/SKILL.md` router is intentionally hidden from slash suggestions and remains available for natural-language requests. + +```bash +ls .claude/skills +.product-loop/bin/boatstack-helper doctor --repo . +``` + +If Boatstack created `.claude/skills/` while Claude Code was already running, reload Claude Code once. Rerun the installer when `doctor` reports a missing generated skill, and never replace a user-owned skill with the same name without reviewing the collision. + ## `/auto-plan` cannot find a source plan Finish the host's Plan-mode exploration and save it. If the host does not expose the path, put exactly one non-empty plan under `.product-loop/intake/`, then rerun `/auto-plan`. Supply an explicit path only when Boatstack reports multiple candidates. diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index 7066bd6..3312fff 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": "9749f1b5397c73f6ea9077719a85f60ae7eee1d6", + "source_commit": "9668feb5b9ba3b816d470dde2cc87c70811a7f9a", "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-claude-command-discovery.md b/release-notes/2026-07-18-claude-command-discovery.md new file mode 100644 index 0000000..b6f3b76 --- /dev/null +++ b/release-notes/2026-07-18-claude-command-discovery.md @@ -0,0 +1,3 @@ +### Claude Code shows Boatstack's main workflow commands + +Claude Code now lists Boatstack's planning, approval, build, evidence-gate, shipping, and update actions directly in slash suggestions. Internal routing, aliases, and retrospectives stay out of the menu while natural-language Boatstack requests continue to work.