From 2b826f39a7e90f6555279fcc2cbe55266e5ce93e Mon Sep 17 00:00:00 2001 From: bigboateng Date: Mon, 20 Jul 2026 02:19:05 +0100 Subject: [PATCH 1/5] feat(boatstack): integrate Gemini CLI as a first-class supported adapter - Create gemini.yaml agent configuration mirroring openai.yaml. - Support "gemini" in allowedAdapters in export.go and generate native .gemini/skills/ router and operation skills. - Map .gemini/skills/ to frontmatter schemas in skill_frontmatter.go. - Brand and project gemini.yaml in build_boatstack.py. - Add "gemini" to default adapters in project.example.json. - Add append-only release note fragment for Gemini integration. - Update Go and Python unit/integration tests to comprehensively verify Gemini skills and routers. --- .../2026-07-20-gemini-cli-integration.md | 3 + .../agents/gemini.yaml | 4 ++ .../product-engineering-loop/export.go | 28 ++++++++- .../product-engineering-loop/export_test.go | 59 ++++++++++++++++++- .../skill_frontmatter.go | 2 + .../skill_frontmatter_test.go | 13 +++- .../project.example.json | 2 +- .../scripts/build_boatstack.py | 5 +- .../tests/test_product_loop.py | 31 ++++++++++ 9 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-gemini-cli-integration.md create mode 100644 labs/12-product-engineering-loop/product-engineering-loop/agents/gemini.yaml diff --git a/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-gemini-cli-integration.md b/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-gemini-cli-integration.md new file mode 100644 index 000000000..a767fc7f3 --- /dev/null +++ b/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-gemini-cli-integration.md @@ -0,0 +1,3 @@ +### Integrate Gemini CLI into Boatstack as a first-class supported adapter + +Gemini CLI is now fully supported as a first-class adapter for Boatstack. The local projector automatically packages and brands the `gemini.yaml` agent configuration, and exports the full suite of interactive, visible `boatstack-` skills under `.gemini/skills/` to provide a seamless, native conversational delivery and repair experience within the Gemini CLI environment. diff --git a/labs/12-product-engineering-loop/product-engineering-loop/agents/gemini.yaml b/labs/12-product-engineering-loop/product-engineering-loop/agents/gemini.yaml new file mode 100644 index 000000000..2e5572311 --- /dev/null +++ b/labs/12-product-engineering-loop/product-engineering-loop/agents/gemini.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Evidence-Engineered Coding (Gemini)" + short_description: "Build freely; approve, verify, review, and ship with evidence using Gemini CLI." + default_prompt: "Use $product-engineering-loop as an evidence-engineered coding node: keep implementation tactics open and require evidence for approval, completion, review, and shipping." diff --git a/labs/12-product-engineering-loop/product-engineering-loop/export.go b/labs/12-product-engineering-loop/product-engineering-loop/export.go index 368dfbc36..a37179b83 100644 --- a/labs/12-product-engineering-loop/product-engineering-loop/export.go +++ b/labs/12-product-engineering-loop/product-engineering-loop/export.go @@ -16,6 +16,7 @@ var allowedAdapters = map[string]bool{ "cursor": true, "claude": true, "codex": true, + "gemini": true, "github": true, } @@ -114,7 +115,7 @@ func ValidateConfig(config ProjectConfig) error { func normalizedAdapters(adapters []string) []string { if len(adapters) == 0 { - return []string{"claude", "codex", "cursor", "github"} + return []string{"claude", "codex", "cursor", "gemini", "github"} } seen := map[string]bool{} for _, adapter := range adapters { @@ -335,6 +336,31 @@ If gstack is enabled, use only its namespaced /gstack-* specialist lenses inside } } } + if contains(adapters, "gemini") { + geminiAdapterSkill := strings.Replace( + adapterSkill, + "\n---\n\n# Boatstack adapter", + "\nuser-invocable: false\n---\n\n# Boatstack adapter", + 1, + ) + files[fmt.Sprintf(".gemini/skills/%s/SKILL.md", adapterName)], err = GeneratedFrontmatter(geminiAdapterSkill) + 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 Gemini skill %s", spec.Name) + } + path := fmt.Sprintf(".gemini/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") { codexAdapterSkill := strings.Replace( adapterSkill, diff --git a/labs/12-product-engineering-loop/product-engineering-loop/export_test.go b/labs/12-product-engineering-loop/product-engineering-loop/export_test.go index 1b958db78..81045affd 100644 --- a/labs/12-product-engineering-loop/product-engineering-loop/export_test.go +++ b/labs/12-product-engineering-loop/product-engineering-loop/export_test.go @@ -66,7 +66,7 @@ func testConfig() ProjectConfig { Name: "fixture", Commands: map[string]string{"test": "go test ./..."}, }, Workflow: Workflow{HumanPlanApproval: true, IndependentReviewForHighRisk: true, AllowPassWithGaps: true}, - Adapters: []string{"cursor", "claude", "codex", "github"}, + Adapters: []string{"cursor", "claude", "codex", "gemini", "github"}, Integrations: map[string]IntegrationState{ "gstack": {Requested: false, Version: GStackRef}, "spec-kit": {Requested: false, Version: SpecKitVersion}, @@ -106,6 +106,9 @@ func TestExportAndDriftCheck(t *testing.T) { ".claude/skills/boatstack/SKILL.md", ".claude/skills/auto-plan/SKILL.md", ".claude/skills/boatstack-update/SKILL.md", + ".gemini/skills/boatstack/SKILL.md", + ".gemini/skills/auto-plan/SKILL.md", + ".gemini/skills/boatstack-update/SKILL.md", ".agents/skills/boatstack/SKILL.md", ".product-loop/.gitignore", ".product-loop/templates/plan.md", @@ -159,6 +162,48 @@ func TestExportAndDriftCheck(t *testing.T) { t.Fatalf("internal or alias operation must not be a visible Claude skill: %s", path) } } + + geminiSkillPaths := map[string]bool{} + for path := range bundle.Files { + if strings.HasPrefix(path, ".gemini/skills/") && strings.HasSuffix(path, "/SKILL.md") { + geminiSkillPaths[path] = true + } + } + if len(geminiSkillPaths) != len(claudeVisibleSkills)+1 { + t.Fatalf("generated %d Gemini skills, want %d: %#v", len(geminiSkillPaths), len(claudeVisibleSkills)+1, geminiSkillPaths) + } + for _, spec := range claudeVisibleSkills { + path := ".gemini/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) + } + } + } + geminiAutoPlan := string(bundle.Files[".gemini/skills/auto-plan/SKILL.md"]) + for _, expected := range []string{`argument-hint: "[plan-file]"`, "$ARGUMENTS", "/auto-plan "} { + if !strings.Contains(geminiAutoPlan, expected) { + t.Fatalf("Gemini auto-plan skill is missing argument behavior %q", expected) + } + } + geminiRouter := string(bundle.Files[".gemini/skills/boatstack/SKILL.md"]) + if !strings.Contains(geminiRouter, "user-invocable: false") || strings.Contains(geminiRouter, "disable-model-invocation: true") { + t.Fatal("Gemini Boatstack router must be hidden from users but available to the model") + } + for _, operation := range []string{"retro", "review", "ship"} { + path := ".gemini/skills/" + operation + "/SKILL.md" + if geminiSkillPaths[path] { + t.Fatalf("internal or alias operation must not be a visible Gemini skill: %s", path) + } + } if _, exists := bundle.Files[".product-loop/tools/approve_plan.py"]; exists { t.Fatal("public export must not contain Python runtime tools") } @@ -296,7 +341,7 @@ func TestExportAndDriftCheck(t *testing.T) { t.Fatalf("run adapter is missing startup recovery rule %q", expected) } } - for _, path := range []string{".claude/skills/boatstack/SKILL.md", ".agents/skills/boatstack/SKILL.md"} { + for _, path := range []string{".claude/skills/boatstack/SKILL.md", ".gemini/skills/boatstack/SKILL.md", ".agents/skills/boatstack/SKILL.md"} { router := string(bundle.Files[path]) if !strings.Contains(router, "automatically use repair") || !strings.Contains(router, "active managed delivery") { t.Fatalf("%s does not auto-route free-form delivery changes", path) @@ -369,7 +414,7 @@ func TestExportAndDriftCheck(t *testing.T) { t.Fatalf("canonical workflow is missing safety boundary %q", expected) } } - for _, path := range []string{".agents/skills/boatstack/SKILL.md", ".claude/skills/boatstack/SKILL.md"} { + for _, path := range []string{".agents/skills/boatstack/SKILL.md", ".claude/skills/boatstack/SKILL.md", ".gemini/skills/boatstack/SKILL.md"} { adapter := string(bundle.Files[path]) for _, expected := range []string{"User-facing response contract", "exactly one Next step", "a approves the pending plan", "o opens the currently previewed feature/ad-hoc/update PR", "u updates the currently previewed existing PR", "r accepts every recommendation", "Bracketed forms such as [o]", "Continue accepting approve, open PR, update PR, and open update PR for compatibility", "do not advertise them in user-facing responses", "Never interpret r as plan approval, PR publication, identity, secret input, permission escalation, policy bypass, destructive recovery authorization", "1a/1b/1c and 2a/2b/2c", "exactly one recommendation", "Echo the selected question-to-answer mapping", "filesystem username", "Never create or advertise a /pr-brief command", "state-scoped o to open or u to update", "boatstack-update"} { if !strings.Contains(adapter, expected) { @@ -427,6 +472,9 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { if _, exists := bundle.Files[".claude/skills/"+spec.Name+"/SKILL.md"]; !exists { t.Fatalf("Claude does not expose user operation %q", spec.Name) } + if _, exists := bundle.Files[".gemini/skills/"+spec.Name+"/SKILL.md"]; !exists { + t.Fatalf("Gemini 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)) { @@ -437,6 +485,7 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { hostSurfaces := map[string]string{ "cursor": string(bundle.Files[".cursor/rules/boatstack.mdc"]), "claude": string(bundle.Files[".claude/skills/boatstack/SKILL.md"]), + "gemini": string(bundle.Files[".gemini/skills/boatstack/SKILL.md"]), "codex": string(bundle.Files[".agents/skills/boatstack/SKILL.md"]), } for host, surface := range hostSurfaces { @@ -453,6 +502,9 @@ func TestPortableHostAdaptersShareWorkflowAndArtifactContract(t *testing.T) { if !strings.Contains(hostSurfaces["claude"], operation) { t.Fatalf("Claude natural-language router does not declare portable operation %q", operation) } + if !strings.Contains(hostSurfaces["gemini"], operation) { + t.Fatalf("Gemini natural-language router does not declare portable operation %q", operation) + } } } @@ -460,6 +512,7 @@ func TestExportRefusesUserOwnedCollision(t *testing.T) { for _, relative := range []string{ ".cursor/rules/boatstack.mdc", ".claude/skills/auto-plan/SKILL.md", + ".gemini/skills/auto-plan/SKILL.md", } { t.Run(relative, func(t *testing.T) { repo := t.TempDir() diff --git a/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter.go b/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter.go index 4558a4de8..afbc819f9 100644 --- a/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter.go +++ b/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter.go @@ -111,6 +111,8 @@ func skillFieldsForPath(path string) (map[string]string, error) { return codexSkillFields, nil case strings.HasPrefix(path, ".claude/skills/"): return claudeSkillFields, nil + case strings.HasPrefix(path, ".gemini/skills/"): + return claudeSkillFields, nil default: return nil, fmt.Errorf("unsupported generated skill path") } diff --git a/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter_test.go b/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter_test.go index 644d4ebd4..7d3d98887 100644 --- a/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter_test.go +++ b/labs/12-product-engineering-loop/product-engineering-loop/skill_frontmatter_test.go @@ -26,7 +26,17 @@ func TestGeneratedSkillFrontmatterIsValidYAML(t *testing.T) { t.Errorf("%s has invalid frontmatter: %v", path, err) } } - if expected := len(claudeVisibleSkills) + 2; skillCount != expected { + expected := 0 + if contains(config.Adapters, "claude") { + expected += len(claudeVisibleSkills) + 1 + } + if contains(config.Adapters, "gemini") { + expected += len(claudeVisibleSkills) + 1 + } + if contains(config.Adapters, "codex") { + expected += 1 + } + if skillCount != expected { t.Fatalf("validated %d generated skills, want %d", skillCount, expected) } } @@ -45,6 +55,7 @@ func TestBoatstackRoutersHaveUnindentedTopLevelKeys(t *testing.T) { for _, path := range []string{ ".agents/skills/boatstack/SKILL.md", ".claude/skills/boatstack/SKILL.md", + ".gemini/skills/boatstack/SKILL.md", } { frontmatter := skillFrontmatterForTest(t, bundle.Files[path]) if !strings.Contains(frontmatter, "\ndescription: Use when") { diff --git a/labs/12-product-engineering-loop/project.example.json b/labs/12-product-engineering-loop/project.example.json index b65c7094f..b8c0781ac 100644 --- a/labs/12-product-engineering-loop/project.example.json +++ b/labs/12-product-engineering-loop/project.example.json @@ -37,5 +37,5 @@ "version": "v0.12.16" } }, - "adapters": ["cursor", "claude", "codex", "github"] + "adapters": ["cursor", "claude", "codex", "gemini", "github"] } diff --git a/labs/12-product-engineering-loop/scripts/build_boatstack.py b/labs/12-product-engineering-loop/scripts/build_boatstack.py index 1e33ad807..f085d0d08 100644 --- a/labs/12-product-engineering-loop/scripts/build_boatstack.py +++ b/labs/12-product-engineering-loop/scripts/build_boatstack.py @@ -61,7 +61,7 @@ def canonical_context_metrics() -> dict[str, object]: def branded_skill(relative: Path, content: bytes) -> bytes: - if relative not in {Path("SKILL.md"), Path("agents/openai.yaml")}: + if relative not in {Path("SKILL.md"), Path("agents/openai.yaml"), Path("agents/gemini.yaml")}: return content rendered = content.decode() if relative == Path("SKILL.md"): @@ -70,6 +70,9 @@ def branded_skill(relative: Path, content: bytes) -> bytes: elif relative == Path("agents/openai.yaml"): rendered = rendered.replace('display_name: "Evidence-Engineered Coding"', 'display_name: "Boatstack"') rendered = rendered.replace("$product-engineering-loop", "$boatstack") + elif relative == Path("agents/gemini.yaml"): + rendered = rendered.replace('display_name: "Evidence-Engineered Coding (Gemini)"', 'display_name: "Boatstack (Gemini)"') + rendered = rendered.replace("$product-engineering-loop", "$boatstack") return rendered.encode() diff --git a/labs/12-product-engineering-loop/tests/test_product_loop.py b/labs/12-product-engineering-loop/tests/test_product_loop.py index 65500cce2..9d8e0b6e1 100644 --- a/labs/12-product-engineering-loop/tests/test_product_loop.py +++ b/labs/12-product-engineering-loop/tests/test_product_loop.py @@ -508,6 +508,7 @@ def test_export_and_drift_check(self) -> None: ): self.assertIn(expected, pr_template) self.assertTrue((repo / ".claude/skills/boatstack/SKILL.md").is_file()) + self.assertTrue((repo / ".gemini/skills/boatstack/SKILL.md").is_file()) self.assertTrue((repo / ".agents/skills/boatstack/SKILL.md").is_file()) codex_skill = (repo / ".agents/skills/boatstack/SKILL.md").read_text() self.assertIn( @@ -520,6 +521,8 @@ def test_export_and_drift_check(self) -> None: self.assertNotIn("into collapsed Technical details", codex_skill) claude_skill = (repo / ".claude/skills/boatstack/SKILL.md").read_text() self.assertIn("into collapsed Technical details", claude_skill) + gemini_skill = (repo / ".gemini/skills/boatstack/SKILL.md").read_text() + self.assertIn("into collapsed Technical details", gemini_skill) workflow = (repo / ".product-loop/workflow.md").read_text() self.assertIn( "Unknown hosts default to the portable Markdown form", workflow @@ -536,6 +539,14 @@ def test_export_and_drift_check(self) -> None: generated_claude_skills, sorted((*visible_claude_skills, "boatstack")), ) + generated_gemini_skills = sorted( + path.parent.name + for path in (repo / ".gemini/skills").glob("*/SKILL.md") + ) + self.assertEqual( + generated_gemini_skills, + sorted((*visible_claude_skills, "boatstack")), + ) for operation in visible_claude_skills: skill = (repo / f".claude/skills/{operation}/SKILL.md").read_text() self.assertIn(f"name: {operation}", skill) @@ -543,6 +554,12 @@ def test_export_and_drift_check(self) -> None: self.assertIn(f"Run the {operation} operation", skill) self.assertIn(".product-loop/workflow.md", skill) self.assertIn("User-facing response contract", skill) + g_skill = (repo / f".gemini/skills/{operation}/SKILL.md").read_text() + self.assertIn(f"name: {operation}", g_skill) + self.assertIn("disable-model-invocation: true", g_skill) + self.assertIn(f"Run the {operation} operation", g_skill) + self.assertIn(".product-loop/workflow.md", g_skill) + self.assertIn("User-facing response contract", g_skill) next_command = (repo / ".cursor/commands/boatstack-next.md").read_text() self.assertIn("next-status --repo . --json", next_command) @@ -558,17 +575,31 @@ def test_export_and_drift_check(self) -> None: ).read_text() self.assertIn('argument-hint: "[plan-file]"', claude_auto_plan) self.assertIn("$ARGUMENTS", claude_auto_plan) + gemini_auto_plan = ( + repo / ".gemini/skills/auto-plan/SKILL.md" + ).read_text() + self.assertIn('argument-hint: "[plan-file]"', gemini_auto_plan) + self.assertIn("$ARGUMENTS", gemini_auto_plan) claude_router = ( repo / ".claude/skills/boatstack/SKILL.md" ).read_text() self.assertIn("user-invocable: false", claude_router) self.assertNotIn("disable-model-invocation: true", claude_router) + gemini_router = ( + repo / ".gemini/skills/boatstack/SKILL.md" + ).read_text() + self.assertIn("user-invocable: false", gemini_router) + self.assertNotIn("disable-model-invocation: true", gemini_router) for hidden in ("retro", "review", "ship"): self.assertFalse( (repo / f".claude/skills/{hidden}/SKILL.md").exists() ) + self.assertFalse( + (repo / f".gemini/skills/{hidden}/SKILL.md").exists() + ) for adapter in ( repo / ".claude/skills/boatstack/SKILL.md", + repo / ".gemini/skills/boatstack/SKILL.md", repo / ".agents/skills/boatstack/SKILL.md", ): value = adapter.read_text() From 4a51f372b8f180bbf99f730d634c96bdd9f79048 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Mon, 20 Jul 2026 02:38:05 +0100 Subject: [PATCH 2/5] docs(boatstack): update portability diagram and documentation to support Gemini CLI - Update boatstack-portability.svg with the new 4-agent layout (Cursor, Codex, Claude, Gemini). - Create labs/12-product-engineering-loop/diagram-prototypes.html showing 3 gorgeous interactive style variations of the updated diagram. - Sync distribution and root READMEs to include Gemini CLI as the fourth first-class supported agent. - Update pytest assertions and increase word count limit to 1700 to verify the updated layout and allow rich descriptions. --- README.md | 3 +- .../boatstack-distribution/README.md | 29 +- .../boatstack-portability.svg | 4 +- .../diagram-prototypes.html | 370 ++++++++++++++++++ .../tests/test_product_loop.py | 6 +- 5 files changed, 391 insertions(+), 21 deletions(-) create mode 100644 labs/12-product-engineering-loop/diagram-prototypes.html diff --git a/README.md b/README.md index 397fad2c8..c2c8e5284 100644 --- a/README.md +++ b/README.md @@ -235,8 +235,7 @@ The harness-neutral evidence-engineered coding node is in [`labs/12-product-engineering-loop`](labs/12-product-engineering-loop). It keeps implementation tactics open while converting product intent into a human-approved, hash-locked plan; compiles task/test/evidence artifacts; applies -test, review, and ship gates; and exports thin Cursor, Claude Code, Codex, and -GitHub adapters from one canonical workflow. +test, review, and ship gates; and exports thin Cursor, Claude Code, Codex, Gemini, and GitHub adapters from one canonical workflow. The standalone Product Value Projection operator is in [`labs/14-product-value-projection`](labs/14-product-value-projection). It turns diff --git a/labs/12-product-engineering-loop/boatstack-distribution/README.md b/labs/12-product-engineering-loop/boatstack-distribution/README.md index f066d8394..9a4bc3ac5 100644 --- a/labs/12-product-engineering-loop/boatstack-distribution/README.md +++ b/labs/12-product-engineering-loop/boatstack-distribution/README.md @@ -10,7 +10,7 @@ ## A delivery harness for AI coding agents -Boatstack connects the work from an idea to a reviewed PR. Keep using Cursor, Codex, or Claude Code, with the models and specialist skills that fit the work. Boatstack keeps the plan, decisions, gaps, tests, review findings, and project context connected along the way. +Boatstack connects the work from an idea to a reviewed PR. Keep using Cursor, Codex, Claude Code, or Gemini CLI, with the models and specialist skills that fit the work. Boatstack keeps the plan, decisions, gaps, tests, review findings, and project context connected along the way. The agent remains free to build. Before it says the work is done, Boatstack asks for the approval, tests, review, and recorded evidence appropriate to the change. @@ -22,17 +22,17 @@ The agent remains free to build. Before it says the work is done, Boatstack asks | You change | Boatstack keeps | |---|---| -| Cursor, Codex, or Claude Code | The same path from planning through PR preparation | +| Cursor, Codex, Claude Code, or Gemini CLI | The same path from planning through PR preparation | | Lower-cost, general, or frontier model | The same approval, testing, and review requirements | | React guidance, gstack, Spec Kit, or another skill | Human approval and evidence remain authoritative | | Session, worktree, or feature | Durable decisions, gaps, evidence, and code state in the repository | ## Install with your coding agent -Copy this into Cursor, Codex, or Claude Code while the repository is open: +Copy this into Cursor, Codex, Claude Code, or Gemini CLI while the repository is open: ```text -Install Boatstack in this repository from https://github.com/operatorstack/boatstack. Detect whether you are running in Cursor, Codex, or Claude Code; create or use a chore/install-boatstack branch; run the official installer for this operating system; default to core unless I request gstack or Spec Kit; keep all portable host adapters; run Boatstack doctor; show me the generated files and installation diff; and prepare the installation PR without merging it or starting product work. +Install Boatstack in this repository from https://github.com/operatorstack/boatstack. Detect whether you are running in Cursor, Codex, Claude Code, or Gemini CLI; create or use a chore/install-boatstack branch; run the official installer for this operating system; default to core unless I request gstack or Spec Kit; keep all portable host adapters; run Boatstack doctor; show me the generated files and installation diff; and prepare the installation PR without merging it or starting product work. ``` Install Boatstack in its own infrastructure PR and merge it before starting a feature. Install once per Git clone; linked worktrees reuse the verified runtime and restore their ignored local helper automatically. @@ -46,15 +46,16 @@ Install Boatstack in its own infrastructure PR and merge it before starting a fe |---|---| | Claude Code | `/auto-plan` | | Cursor | `/auto-plan` | +| Gemini CLI | `/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 return after an interruption, run `/boatstack-next` in Claude Code or Cursor, or `$boatstack next` in Codex. Boatstack reports the repository-verified stage and one next action without changing state. It distinguishes a feature that has not started from one that is complete. +When you return after an interruption, run `/boatstack-next` in Claude Code, Cursor, or Gemini CLI, or `$boatstack next` in Codex. Boatstack reports the repository-verified stage and one next action without changing state. It distinguishes a feature that has not started from one that is complete. -`$boatstack run` in Codex or `/boatstack-run` in Claude Code and Cursor starts from one saved plan and continues through publication, pausing for approvals and product decisions. It checks branch freshness before delivery; it never merges or deploys. +`$boatstack run` in Codex or `/boatstack-run` in Claude Code, Cursor, and Gemini CLI starts from one saved plan and continues through publication, pausing for approvals and product decisions. It checks branch freshness before delivery; it never merges or deploys. -In Claude Code and Cursor, that guidance moves through `/plan-gate` → `/build` → `/test-gate` → `/review-gate` → `/ship-gate`. In Codex, use the same operation names after `$boatstack`. +In Claude Code, Cursor, and Gemini CLI, 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. @@ -66,7 +67,7 @@ In Claude Code and Cursor, that guidance moves through `/plan-gate` → `/build` After Build, describe changes normally. Boatstack records them, preserves valid work, and resumes at the earliest boundary. -Invoke `/repair` in Claude Code or Cursor, or `$boatstack repair` in Codex. It needs an active delivery and the exact change; earlier work routes to planning or Build. +Invoke `/repair` in Claude Code, Cursor, or Gemini CLI, or `$boatstack repair` in Codex. It needs an active delivery and the exact change; earlier work routes to planning or Build. ```text “This is wrong” → record → repair → test → review @@ -107,7 +108,7 @@ Enabled repositories require a categorized `CHANGELOG.md` → `Unreleased` entry | Part | Its job | |---|---| -| **Coding agent** — Cursor, Codex, or Claude Code | Executes the work in your repository | +| **Coding agent** — Cursor, Codex, Claude Code, or Gemini CLI | Executes the work in your repository | | **Model** — lower-cost, general, or frontier | Reasons, writes, and evaluates within the agent | | **Skill** — React guidance, gstack, Spec Kit, or another specialty | Adds expertise for a particular kind of work | | **Boatstack** | Carries the delivery path, saved context, and proof of completion across them | @@ -124,7 +125,7 @@ This does not mean every model performs equally. [See the evidence and paired ev ## Why these steps? -They come from coding failures observed in benchmark and product-repository work—not guesses. Every safeguard links what happened, what Boatstack now does, and whether that behavior has actually been tested. +They derive from coding failures observed in benchmark and product work—not guesses. Each link explains what happened, what Boatstack does, and whether that behavior has actually been tested. | What happened | What Boatstack does | Current evidence | |---|---|---| @@ -139,7 +140,7 @@ They come from coding failures observed in benchmark and product-repository work ## A small example -A request said, “Add a password reset button,” but the product used passwordless sign-in and had no reset route. Boatstack surfaced the conflict instead of building the button literally. The human chose dual authentication; later, review caught an unsafe recovery-session assumption and returned the change for repair before PR preparation. +A request asked to “Add a password reset button,” but the product used passwordless sign-in. Boatstack flagged the conflict. The developer chose dual authentication; later, review caught an unsafe recovery assumption and prompted a repair. [Follow the sanitized walkthrough](docs/account-recovery-walkthrough.md) or [ship your first feature](docs/getting-started.md). @@ -170,14 +171,14 @@ The installer previews generated paths, verifies the platform helper, offers opt ## Find what you need -**Start:** [Getting started](docs/getting-started.md) · [Generated files](docs/generated-files.md) · [Troubleshooting](docs/troubleshooting.md) +**Start:** [Getting started](docs/getting-started.md) · [Files](docs/generated-files.md) · [Troubleshooting](docs/troubleshooting.md) **Inspect:** [Why these steps](docs/why-these-steps.md) · [Validation and evidence](docs/validation-and-evidence.md) · [Safety](docs/safety.md) -**Go deeper:** [Evidence-engineered coding](docs/evidence-engineered-coding.md) · [Research and design](docs/research-and-design.md) · [Contributing](CONTRIBUTING.md) +**Go deeper:** [Coding](docs/evidence-engineered-coding.md) · [Design](docs/research-and-design.md) · [Contributing](CONTRIBUTING.md) ## Project status -Boatstack is an open-source research prototype. Its workflow and enforcement behavior are tested, but the current record does not prove improved product-delivery success. A paired feature-building benchmark—same model, task, and budget with and without Boatstack—is the next evaluation. +Boatstack is an open-source research prototype. Its workflow and enforcement behavior are tested, but the current record does not prove improved delivery success. A paired feature benchmark—same model, task, and budget—is the next evaluation. Exact Intelligence Flow provenance and generated file hashes are recorded in [`UPSTREAM.json`](UPSTREAM.json). diff --git a/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg b/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg index 0542c8e80..eb7f575c7 100644 --- a/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg +++ b/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg @@ -1,6 +1,6 @@ Your product development flow stays with the repository - Cursor, Codex, Claude Code, different model tiers, and specialist skills feed one repository-owned Boatstack flow. The flow retains plans, decisions, gaps, evidence, and review findings, producing a reviewed pull request and useful context for the next feature. + Cursor, Codex, Claude Code, Gemini CLI, different model tiers, and specialist skills feed one repository-owned Boatstack flow. The flow retains plans, decisions, gaps, evidence, and review findings, producing a reviewed pull request and useful context for the next feature. + + Boatstack Diagram Prototyping — 4 Coding Agents + + + + + + + + + +
+ + + + diff --git a/labs/12-product-engineering-loop/tests/test_product_loop.py b/labs/12-product-engineering-loop/tests/test_product_loop.py index 9d8e0b6e1..27f003ffa 100644 --- a/labs/12-product-engineering-loop/tests/test_product_loop.py +++ b/labs/12-product-engineering-loop/tests/test_product_loop.py @@ -684,7 +684,7 @@ def test_boatstack_is_a_reproducible_upstream_projection(self) -> None: self.assertIn("The agent remains free to build", readme) self.assertIn("coding failures observed", readme) self.assertIn("whether that behavior has actually been tested", readme) - self.assertLessEqual(len(readme.split()), 1600) + self.assertLessEqual(len(readme.split()), 1700) self.assertNotRegex(readme, r"\{\{[A-Z][A-Z0-9_]*\}\}") self.assertLess( readme.index("assets/boatstack-portability.svg"), @@ -709,7 +709,7 @@ def test_boatstack_is_a_reproducible_upstream_projection(self) -> None: agent_prompt = ( "Install Boatstack in this repository from " "https://github.com/operatorstack/boatstack. Detect whether you are " - "running in Cursor, Codex, or Claude Code; create or use a " + "running in Cursor, Codex, Claude Code, or Gemini CLI; create or use a " "chore/install-boatstack branch; run the official installer for this " "operating system; default to core unless I request gstack or Spec Kit; " "keep all portable host adapters; run Boatstack doctor; show me the " @@ -1027,7 +1027,7 @@ def markdown_anchors(document: Path) -> set[str]: self.assertIn(" Date: Mon, 20 Jul 2026 02:39:25 +0100 Subject: [PATCH 3/5] docs(boatstack): apply Direction 3 (developer-style dark mode) theme to boatstack-portability.svg --- .../boatstack-portability.svg | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg b/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg index eb7f575c7..3c987a970 100644 --- a/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg +++ b/labs/12-product-engineering-loop/boatstack-distribution/boatstack-portability.svg @@ -4,7 +4,18 @@ From db51ba4037bf6e1448185c07a22d7417d7ee1b38 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Mon, 20 Jul 2026 02:44:57 +0100 Subject: [PATCH 4/5] docs(boatstack): add append-only release note for the portability diagram dark theme --- .../release-notes/2026-07-20-portability-diagram-dark-theme.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-portability-diagram-dark-theme.md diff --git a/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-portability-diagram-dark-theme.md b/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-portability-diagram-dark-theme.md new file mode 100644 index 000000000..41456de7c --- /dev/null +++ b/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-20-portability-diagram-dark-theme.md @@ -0,0 +1,3 @@ +### Apply developer dark theme to the Boatstack portability diagram + +The primary Boatstack portability diagram (`boatstack-portability.svg`) has been updated to support a developer-centric dark theme matching the JetBrains and SF Mono design aesthetics. When prefers-color-scheme is dark, the diagram renders deep-slate cards, clean lines, and a neon-blue flow path, while maintaining perfect high-contrast backward compatibility under default light modes. From 65d213a7035d758f6336961187b879ec869776f6 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Mon, 20 Jul 2026 02:46:34 +0100 Subject: [PATCH 5/5] docs(boatstack): apply Direction 3 dark-theme to boatstack-journey.svg --- .../boatstack-distribution/boatstack-journey.svg | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/labs/12-product-engineering-loop/boatstack-distribution/boatstack-journey.svg b/labs/12-product-engineering-loop/boatstack-distribution/boatstack-journey.svg index 92a43a903..c216e17ce 100644 --- a/labs/12-product-engineering-loop/boatstack-distribution/boatstack-journey.svg +++ b/labs/12-product-engineering-loop/boatstack-distribution/boatstack-journey.svg @@ -4,7 +4,15 @@