diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a74caf..1f99318 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/3c8217d87c7117173b02ce29cae5316ccb6beca4/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/0633de4b4ed8385f180284ae21c10cc2e9761399/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/UPSTREAM.json b/UPSTREAM.json index fdd8260..cb8b0f2 100644 --- a/UPSTREAM.json +++ b/UPSTREAM.json @@ -12,7 +12,7 @@ }, "files": { ".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957", - "CONTRIBUTING.md": "115637ba74936e2d3452359dc3bbe3b3520e7cebcabe696896ab7beb61cf2177", + "CONTRIBUTING.md": "c4a9a512b125fe3b19714b6ec09e9267480ea1653df2d9cf4f4c6173e17884c4", "README.md": "a840004db57a129ecd361bb0421adfe02065d86751736dd91953c8a8b178e5ef", "assets/boatstack-journey.svg": "c1f7fe2741f5e9ca66bb3fe9b103e6364ba5acbca8b7a8054768ffd85cf325ea", "assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4", @@ -36,9 +36,10 @@ "boatstack/cmd/boatstack-helper/main.go": "b4b8b43d80dbf60f15e18885ff8ee01637df6e47c0249938714d885163350539", "boatstack/delivery.go": "565bc8465252a798124a29874e1aeffff16e9027d1a86715e11b3e4bc0743bd2", "boatstack/delivery_test.go": "2545c26389d330a92c11e7b09fb2342101db5d09a825a2a9e81b4ea9eea9407b", - "boatstack/export.go": "48032b870f226b1a2758f332aa5b684fad65c2dd5370ae6447d694f406a6a467", + "boatstack/export.go": "42b438d5b1976836ddb9322a09fa7949c044e2cd2ed0e50d4961329c0184b6a8", "boatstack/export_test.go": "9474985bf2aab4f2f14d37dcc3cedc6d7a74db39739fbbfd7615297e35b079b5", - "boatstack/go.mod": "57c377eccea51372d6664de4169e2ca45806b046f7e8a98a1e35a9eb454b4b8d", + "boatstack/go.mod": "6086ef1b2a83f5696190dca692c653925f27b61f652f659fd3fca43ed54a1641", + "boatstack/go.sum": "26c315c867b11b886f3c9402fce7f341f6a9115a5d61f54afbb5e1b1fb5f6017", "boatstack/hooks.go": "1d5d8c4bf7e6e867c8bf07e391d86158347269f856647a5d256f345bbb8d3c96", "boatstack/hooks_test.go": "c3f359416ea53f258d8747d0247381e8946efd4d4a5bcf072c4147f885475ad3", "boatstack/init.go": "fb863a68a6cced5bd2dcf9cd8ca53f0dab7ef0309830269efd985ad8b8a92059", @@ -67,6 +68,8 @@ "boatstack/runtime_cache_test.go": "4cbca9dec7800d7df6e3ec0d74c7ecbe1508e5c5a288d863f35fc8d22986c308", "boatstack/safety.go": "8bcce4c11094b4018093ac5fb5a5256cc5c63c825ddf7ec1a46b9d0098557d33", "boatstack/safety_test.go": "03885d5a93f42b6adba6eef6bbef0680d81c0e1442fb72626a105da4862aecdf", + "boatstack/skill_frontmatter.go": "85ba7389e5d41f52a716b912ffb6b03c81e575b0b049b2cc11f7cd45c3da93c4", + "boatstack/skill_frontmatter_test.go": "ad92021f44f09d99949bda6670a8e350af85254dee087eeb86f3815dbdf7f500", "boatstack/testdata/reviewer-pr-body.md": "4c64e3788e5d61a377aeb0f797f7fc8d2316ab6e49572d15636eea7ba9e34ac4", "boatstack/testdata/safety/safe_apply.py.txt": "c9ec7fb932cf21b6aa8df597c4d4c54d6ec65e796240e49118d699f583383975", "boatstack/testdata/safety/unsafe_apply.py.txt": "42db1751865cc15c4dd69a03146b5deca8f21f916d258e433b27bbef5f884ab1", @@ -75,10 +78,10 @@ "docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3", "docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6", "docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79", - "docs/evidence-engineered-coding.md": "800027421c4aa40d31b337ecb4aa96edc672911c7e475501f40fd01543e1387f", + "docs/evidence-engineered-coding.md": "e06074a2da10e6d1504bf19a3679095fa93d361fac8d66afc041eca44bac80d6", "docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c", "docs/getting-started.md": "c298c0d78054266099ae98232a4c29976fa4e176d4ace49b6a20a8c13c35bc51", - "docs/public-claims.json": "85a6de5fb913d6e039323d0b7d0dc166f3abae1f231354af034288214ef680e5", + "docs/public-claims.json": "80ed7c175d3288ebe417dc1c4f9b6c60001c4a22453516f64d93c45534020cc3", "docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907", "docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f", "docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6", @@ -92,7 +95,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": "d9320eed4adfd09430d9bb6bc4fd6878cbb32a25e1662e130105f18c40839958", + "labs/diagram-json/plan.lock.json": "ee380e4e0beedc036db86615fb112822614b10037d47a7bc1dbb45f142822f22", "labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51", "labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d", "labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed", @@ -116,12 +119,13 @@ "release-notes/2026-07-18-safety-sql-boundaries.md": "32011ca3d02a371e8f3f2899ffb34df3af0843d18d25e7db95fbca32c2dcf18c", "release-notes/2026-07-18-stacked-bar-mark.md": "c4d5bd5fb89c280d7fba015384fd795fcb8c31ffe501078aa55a90cbcf66ba7b", "release-notes/2026-07-18-startup-recovery-routing.md": "c305a1c2b8347e1bc6d7fcbd4a8e629438cdeb4bcf6f181a7d6fc154a88f4318", - "release-notes/2026-07-19-optional-repository-changelog.md": "7ae6bed436c269335ac5055e04438489451bef00836fecb3e5fcb06a18501853" + "release-notes/2026-07-19-optional-repository-changelog.md": "7ae6bed436c269335ac5055e04438489451bef00836fecb3e5fcb06a18501853", + "release-notes/2026-07-19-valid-skill-frontmatter.md": "a0a002f7891474d7d1eea49e3234cae5ddbd941158726b83555dc48474181a92" }, "generator": "operatorstack/intelligence-flow:boatstack-distribution", "schema_version": 1, "source": { - "commit": "3c8217d87c7117173b02ce29cae5316ccb6beca4", + "commit": "0633de4b4ed8385f180284ae21c10cc2e9761399", "path": "labs/12-product-engineering-loop", "repository": "operatorstack/intelligence-flow" } diff --git a/boatstack/export.go b/boatstack/export.go index 45217d6..975cc56 100644 --- a/boatstack/export.go +++ b/boatstack/export.go @@ -279,7 +279,7 @@ 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, 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. +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 @@ -388,6 +388,9 @@ List explicit gaps with impact and revisit trigger, or state that no material ga `) } + if err := validateGeneratedSkills(files); err != nil { + return ExportBundle{}, err + } hashes := map[string]string{} for path, value := range files { diff --git a/boatstack/go.mod b/boatstack/go.mod index e57a8c5..8e4c22b 100644 --- a/boatstack/go.mod +++ b/boatstack/go.mod @@ -1,3 +1,5 @@ module github.com/operatorstack/boatstack/boatstack go 1.26 + +require go.yaml.in/yaml/v3 v3.0.4 diff --git a/boatstack/go.sum b/boatstack/go.sum new file mode 100644 index 0000000..56a75c7 --- /dev/null +++ b/boatstack/go.sum @@ -0,0 +1,4 @@ +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/boatstack/skill_frontmatter.go b/boatstack/skill_frontmatter.go new file mode 100644 index 0000000..4558a4d --- /dev/null +++ b/boatstack/skill_frontmatter.go @@ -0,0 +1,124 @@ +package boatstack + +import ( + "fmt" + "io" + "path/filepath" + "strings" + + "go.yaml.in/yaml/v3" +) + +var codexSkillFields = map[string]string{ + "name": "!!str", + "description": "!!str", +} + +var claudeSkillFields = map[string]string{ + "name": "!!str", + "description": "!!str", + "argument-hint": "!!str", + "user-invocable": "!!bool", + "disable-model-invocation": "!!bool", +} + +func validateGeneratedSkills(files map[string][]byte) error { + for _, path := range sortedKeys(files) { + if !strings.HasSuffix(path, "/SKILL.md") { + continue + } + if err := validateSkillFrontmatter(path, files[path]); err != nil { + return fmt.Errorf("validate generated skill %s: %w", path, err) + } + } + return nil +} + +func validateSkillFrontmatter(path string, raw []byte) error { + const opening = "---\n" + if !strings.HasPrefix(string(raw), opening) { + return fmt.Errorf("must start with YAML frontmatter delimiter ---") + } + closing := strings.Index(string(raw[len(opening):]), "\n---\n") + if closing < 0 { + return fmt.Errorf("frontmatter is missing its closing ---") + } + frontmatter := raw[len(opening) : len(opening)+closing] + if strings.ContainsRune(string(frontmatter), '\t') { + return fmt.Errorf("frontmatter must not contain tabs") + } + + var document yaml.Node + decoder := yaml.NewDecoder(strings.NewReader(string(frontmatter))) + if err := decoder.Decode(&document); err != nil { + return fmt.Errorf("parse YAML frontmatter: %w", err) + } + var extra yaml.Node + if err := decoder.Decode(&extra); err != io.EOF { + if err == nil { + return fmt.Errorf("frontmatter must contain exactly one YAML document") + } + return fmt.Errorf("parse YAML frontmatter: %w", err) + } + if len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode { + return fmt.Errorf("frontmatter must be one YAML mapping") + } + + allowed, err := skillFieldsForPath(path) + if err != nil { + return err + } + root := document.Content[0] + fields := map[string]*yaml.Node{} + for index := 0; index < len(root.Content); index += 2 { + key := root.Content[index] + value := root.Content[index+1] + if key.Kind != yaml.ScalarNode || key.Tag != "!!str" { + return fmt.Errorf("frontmatter keys must be strings") + } + if key.Column != 1 { + return fmt.Errorf("top-level key %q must start at column 1", key.Value) + } + expectedTag, ok := allowed[key.Value] + if !ok { + return fmt.Errorf("unsupported frontmatter field %q", key.Value) + } + if _, duplicate := fields[key.Value]; duplicate { + return fmt.Errorf("duplicate frontmatter field %q", key.Value) + } + if value.Kind != yaml.ScalarNode || value.Tag != expectedTag { + return fmt.Errorf("frontmatter field %q must have type %s", key.Value, yamlTypeName(expectedTag)) + } + fields[key.Value] = value + } + + for _, required := range []string{"name", "description"} { + value, ok := fields[required] + if !ok || strings.TrimSpace(value.Value) == "" { + return fmt.Errorf("frontmatter field %q is required and must not be empty", required) + } + } + expectedName := filepath.Base(filepath.Dir(filepath.FromSlash(path))) + if fields["name"].Value != expectedName { + return fmt.Errorf("frontmatter name %q must match skill directory %q", fields["name"].Value, expectedName) + } + return nil +} + +func skillFieldsForPath(path string) (map[string]string, error) { + switch { + case strings.HasPrefix(path, ".agents/skills/"): + return codexSkillFields, nil + case strings.HasPrefix(path, ".claude/skills/"): + return claudeSkillFields, nil + default: + return nil, fmt.Errorf("unsupported generated skill path") + } +} + +func yamlTypeName(tag string) string { + if tag == "!!bool" { + return "boolean" + } + return "string" +} diff --git a/boatstack/skill_frontmatter_test.go b/boatstack/skill_frontmatter_test.go new file mode 100644 index 0000000..644d4eb --- /dev/null +++ b/boatstack/skill_frontmatter_test.go @@ -0,0 +1,112 @@ +package boatstack + +import ( + "strings" + "testing" +) + +func TestGeneratedSkillFrontmatterIsValidYAML(t *testing.T) { + config := testConfig() + raw, err := MarshalJSON(config) + if err != nil { + t.Fatal(err) + } + bundle, err := BuildExportBundle(".boatstack-project.json", config, raw, "boatstack") + if err != nil { + t.Fatal(err) + } + + skillCount := 0 + for path, value := range bundle.Files { + if !strings.HasSuffix(path, "/SKILL.md") { + continue + } + skillCount++ + if err := validateSkillFrontmatter(path, value); err != nil { + t.Errorf("%s has invalid frontmatter: %v", path, err) + } + } + if expected := len(claudeVisibleSkills) + 2; skillCount != expected { + t.Fatalf("validated %d generated skills, want %d", skillCount, expected) + } +} + +func TestBoatstackRoutersHaveUnindentedTopLevelKeys(t *testing.T) { + config := testConfig() + raw, err := MarshalJSON(config) + if err != nil { + t.Fatal(err) + } + bundle, err := BuildExportBundle(".boatstack-project.json", config, raw, "boatstack") + if err != nil { + t.Fatal(err) + } + + for _, path := range []string{ + ".agents/skills/boatstack/SKILL.md", + ".claude/skills/boatstack/SKILL.md", + } { + frontmatter := skillFrontmatterForTest(t, bundle.Files[path]) + if !strings.Contains(frontmatter, "\ndescription: Use when") { + t.Errorf("%s description is not at column 1:\n%s", path, frontmatter) + } + for _, line := range strings.Split(frontmatter, "\n") { + if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { + t.Errorf("%s has an indented top-level key %q", path, line) + } + } + } +} + +func TestValidateSkillFrontmatterRejectsMalformedMetadata(t *testing.T) { + validCodex := "---\nname: boatstack\ndescription: Use when testing.\n---\n\n# Boatstack\n" + validClaude := "---\nname: boatstack\ndescription: Use when testing.\nuser-invocable: false\n---\n\n# Boatstack\n" + tests := []struct { + name string + path string + raw string + want string + }{ + {"tab indentation", ".agents/skills/boatstack/SKILL.md", "---\nname: boatstack\n\tdescription: broken\n---\n", "must not contain tabs"}, + {"space indentation", ".agents/skills/boatstack/SKILL.md", "---\n name: boatstack\n description: broken\n---\n", "column 1"}, + {"invalid YAML", ".agents/skills/boatstack/SKILL.md", "---\nname: [\ndescription: broken\n---\n", "parse YAML"}, + {"missing opening delimiter", ".agents/skills/boatstack/SKILL.md", "name: boatstack\n", "must start"}, + {"missing closing delimiter", ".agents/skills/boatstack/SKILL.md", "---\nname: boatstack\n", "missing its closing"}, + {"duplicate field", ".agents/skills/boatstack/SKILL.md", "---\nname: boatstack\nname: boatstack\ndescription: duplicate\n---\n", "duplicate"}, + {"missing description", ".agents/skills/boatstack/SKILL.md", "---\nname: boatstack\n---\n", "description"}, + {"empty description", ".agents/skills/boatstack/SKILL.md", "---\nname: boatstack\ndescription: ' '\n---\n", "must not be empty"}, + {"unsupported Codex field", ".agents/skills/boatstack/SKILL.md", strings.Replace(validCodex, "description:", "user-invocable: false\ndescription:", 1), "unsupported"}, + {"wrong Claude type", ".claude/skills/boatstack/SKILL.md", strings.Replace(validClaude, "user-invocable: false", "user-invocable: no", 1), "boolean"}, + {"directory mismatch", ".agents/skills/other/SKILL.md", validCodex, "must match skill directory"}, + {"unsupported host", ".other/skills/boatstack/SKILL.md", validCodex, "unsupported generated skill path"}, + {"non-mapping", ".agents/skills/boatstack/SKILL.md", "---\n- name\n- description\n---\n", "one YAML mapping"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := validateSkillFrontmatter(test.path, []byte(test.raw)) + if err == nil || !strings.Contains(err.Error(), test.want) { + t.Fatalf("got %v, want error containing %q", err, test.want) + } + }) + } +} + +func TestGeneratedSkillHarnessRejectsInvalidBundle(t *testing.T) { + files := map[string][]byte{ + ".agents/skills/boatstack/SKILL.md": []byte("---\nname: boatstack\n\tdescription: broken\n---\n"), + } + err := validateGeneratedSkills(files) + if err == nil || !strings.Contains(err.Error(), ".agents/skills/boatstack/SKILL.md") { + t.Fatalf("generated-skill boundary did not name and reject invalid output: %v", err) + } +} + +func skillFrontmatterForTest(t *testing.T, raw []byte) string { + t.Helper() + value := string(raw) + closing := strings.Index(value[4:], "\n---\n") + if !strings.HasPrefix(value, "---\n") || closing < 0 { + t.Fatalf("test fixture lacks frontmatter delimiters: %q", value) + } + return value[:4+closing] +} diff --git a/docs/evidence-engineered-coding.md b/docs/evidence-engineered-coding.md index d45948e..af8728f 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 [`3c8217d87c7117173b02ce29cae5316ccb6beca4`](https://github.com/operatorstack/intelligence-flow/tree/3c8217d87c7117173b02ce29cae5316ccb6beca4/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 [`0633de4b4ed8385f180284ae21c10cc2e9761399`](https://github.com/operatorstack/intelligence-flow/tree/0633de4b4ed8385f180284ae21c10cc2e9761399/labs/12-product-engineering-loop). The evidence supports specific failure mechanisms and guardrails. It does not establish that Boatstack is optimal, that control-theory notation proves software quality, or that one workflow dominates every team. Those are evaluation questions, so the distribution preserves measurements, provenance, gaps, and negative results. diff --git a/docs/public-claims.json b/docs/public-claims.json index 34557f2..2899565 100644 --- a/docs/public-claims.json +++ b/docs/public-claims.json @@ -1,6 +1,6 @@ { "schema_version": 1, - "source_commit": "3c8217d87c7117173b02ce29cae5316ccb6beca4", + "source_commit": "0633de4b4ed8385f180284ae21c10cc2e9761399", "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" }, { "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:3c8217d87c7117173b02ce29cae5316ccb6beca4" + "last_verified_version": "source:0633de4b4ed8385f180284ae21c10cc2e9761399" } ] } diff --git a/labs/diagram-json/plan.lock.json b/labs/diagram-json/plan.lock.json index dddecfc..a9dca3f 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": "3c8217d87c7117173b02ce29cae5316ccb6beca4", + "source_commit": "0633de4b4ed8385f180284ae21c10cc2e9761399", "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-19-valid-skill-frontmatter.md b/release-notes/2026-07-19-valid-skill-frontmatter.md new file mode 100644 index 0000000..141f759 --- /dev/null +++ b/release-notes/2026-07-19-valid-skill-frontmatter.md @@ -0,0 +1,3 @@ +### Validate generated skill metadata before installation + +Boatstack now emits valid YAML frontmatter for its Codex and Claude skill adapters and rejects malformed or host-incompatible skill metadata before installation. Codex can discover `$boatstack` again, while export checks and doctor prevent future adapter-format regressions from being reported as healthy.