Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/340059469cf115431f0f91c0a88beb34374d1e5a/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/17376b5e74eb69d341c6df1b69251f2e07d47c4f/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.

Expand Down
27 changes: 14 additions & 13 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"canonical_context": {
"characters": 43677,
"estimated_tokens": 10920,
"characters": 43912,
"estimated_tokens": 10978,
"estimator": "ceil(total characters / 4); compactness signal, not provider billing",
"files": [
"product-engineering-loop/references/workflow.md",
Expand All @@ -12,7 +12,7 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "79338a8a60c24719b8618b3ffe5b6112075d4035d9c2aec07a55aa50f09daa4c",
"CONTRIBUTING.md": "f679c93d361def0a2c8903c1b5714d4bf707add3eeeeec88370b8ceef936a8e0",
"README.md": "a840004db57a129ecd361bb0421adfe02065d86751736dd91953c8a8b178e5ef",
"assets/boatstack-journey.svg": "c1f7fe2741f5e9ca66bb3fe9b103e6364ba5acbca8b7a8054768ffd85cf325ea",
"assets/boatstack-mark.svg": "ec96165583b15cfd446c27049d49217973f3e9b1defa5771cc08eec0c9542ce4",
Expand All @@ -31,14 +31,14 @@
"boatstack/assets/templates/test-plan.md": "6db8a9f27dd171fb80222a501cae50eb051e7278c04703fa43b5ff86dd4d2df4",
"boatstack/atomic_unix.go": "89f2723361591de2bb8bd22ce7e34ec529d3278509f0df78fd5c4a7d4140fbe9",
"boatstack/atomic_windows.go": "cefd775cbe7e7c3bd8a3f5673b11cdd784c6d3ebd6de7dcb8f39406b0bee511f",
"boatstack/changelog.go": "5f0b1e7a66586c58aad03bfea8126178316f82ff6583bd67d35d6fcf71a6f9bc",
"boatstack/changelog_test.go": "2cee0d580dc2fa0752ee58564d030961b20139008de132ee8abff9e6b6713e28",
"boatstack/changelog.go": "c5e1f31440b44d61e6037ad27af0333540af3545d655e35819a0241cbbebd8ec",
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
"boatstack/cmd/boatstack-helper/main.go": "03637dce9ffd5e36355ec8c09bfdf4a9c34769808cdd7d330634e79b5bb9ac77",
"boatstack/cmd/boatstack-helper/main_test.go": "ff73003b6a5157202fa09ddf1129fb13c3d79702b2e05a8721ce5a11bf5ab779",
"boatstack/delivery.go": "565bc8465252a798124a29874e1aeffff16e9027d1a86715e11b3e4bc0743bd2",
"boatstack/delivery_test.go": "2545c26389d330a92c11e7b09fb2342101db5d09a825a2a9e81b4ea9eea9407b",
"boatstack/export.go": "9f4b2b48ea19368232a7d335fc23f632e8a1081736582dab61fd1ba6125850a3",
"boatstack/export_test.go": "9474985bf2aab4f2f14d37dcc3cedc6d7a74db39739fbbfd7615297e35b079b5",
"boatstack/export.go": "786858fbd1a0172371f310937d0f3244cf1f57246e916fdac581e3da8618e67a",
"boatstack/export_test.go": "fbe19b8627c9a66d6e5231aa0e275dccb28f85046a3f2f242a2ca306bb6c2665",
"boatstack/go.mod": "6086ef1b2a83f5696190dca692c653925f27b61f652f659fd3fca43ed54a1641",
"boatstack/go.sum": "26c315c867b11b886f3c9402fce7f341f6a9115a5d61f54afbb5e1b1fb5f6017",
"boatstack/hooks.go": "ed131c526f434d7c3309a388b97f3d28df2a6bdf9bc7322499405d5174ab8518",
Expand All @@ -60,7 +60,7 @@
"boatstack/references/host-hook-contracts.md": "1382213ad004389de6da5a03af43ec28ace6329c9e7a3f07148566cf2ea12727",
"boatstack/references/irreversible-operation-boundary.md": "631743991ace65977586e4537f8dd50f8ae88f8e16f27cf7baad93b2791a73df",
"boatstack/references/portability.md": "fb683095991bb0cb06ec56fb8884c49038b283172a7d2f8b203483b7cacb4bae",
"boatstack/references/workflow.md": "b460b647bdd5d0ce4f1bd1015434a9e6c3b8ef525920ed018fb26a22bbc3aa9a",
"boatstack/references/workflow.md": "a342d519f8420f34b9431c4624fc6f823034218eebb520e74d0cf5f4f3d5a382",
"boatstack/release.go": "fa2ac926df89c90c5844e938a2e02d4b8dbbaefbf85bb7a1a89fc51690bea520",
"boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690",
"boatstack/run.go": "a9afb239c5e2cb80f96dab594f0dc94852f2f16625b1de05c5110ee85f61cfaa",
Expand All @@ -80,10 +80,10 @@
"docs/account-recovery-walkthrough.md": "676034974594a7d1a559b24dbed31d7ccc429eb81404b203ca07bbdaa19ec3d3",
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/evidence-engineered-coding.md": "3e51926eb4a23d1774b93c59e8cf2b670decabcb746bdd0db1dc2a790fa40f1f",
"docs/evidence-engineered-coding.md": "c418dfc92d3e0a38996162f4b4c120693817c93056be15dfa7828295011457eb",
"docs/generated-files.md": "136422baf0c7fc2bd5100cfe0ebdb3d9d0705dfd7e7d54bf745dd1037e63492c",
"docs/getting-started.md": "d57fa781176321dd25f2917488133f912ec1ef1645a3c43f9630db0485049dd8",
"docs/public-claims.json": "44bb7a014da3f53d627d441d4473262c712fdc80a19d979b8c89f906a4078a6e",
"docs/getting-started.md": "eacc814fdffdfa3c7d8052b7cd99a79c04da5c75d88d8b44f3fb68d9afec0316",
"docs/public-claims.json": "df3d78d6c2a9b20d3a6c20bbea1c3d5eb5d7fbedfad60e34cbb121498e9a7c84",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "d65c66e323037bda5d45aacef5d48afa6bf93da55901378891d235aca3a5684f",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -97,7 +97,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": "e28185904ad653ccf43858afaf5b17c07f773e22ed21b8d3f88987e753011a99",
"labs/diagram-json/plan.lock.json": "f18205ea54ae03756bd5bb7513a48ac494b9ed6e5bce8015bd72fee7d8b46c42",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand All @@ -122,13 +122,14 @@
"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-cross-harness-hook-conformance.md": "15dc22cc3b534c6d0bd89f34cd69cabf94a3610d445a039ba111fd8bce727e68",
"release-notes/2026-07-19-minimal-dated-changelogs.md": "6f525e5c5cf26cc72969a161bbdaeb4a5c1f14759c5985335e97e3d3f45b1daf",
"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": "340059469cf115431f0f91c0a88beb34374d1e5a",
"commit": "17376b5e74eb69d341c6df1b69251f2e07d47c4f",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
15 changes: 14 additions & 1 deletion boatstack/changelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"
)

const changelogPath = "CHANGELOG.md"
Expand All @@ -20,6 +21,18 @@ var changelogCategories = map[string]bool{
"Maintenance": true,
}

func isUnreleasedHeading(line string) bool {
if line == "## Unreleased" || line == "## [Unreleased]" {
return true
}
const prefix = "## [Unreleased] - "
if !strings.HasPrefix(line, prefix) {
return false
}
_, err := time.Parse("2006-01-02", strings.TrimPrefix(line, prefix))
return err == nil
}

// changelogEntries returns the categorized bullets in the Unreleased section.
// Historical release sections are intentionally ignored: the policy requires a
// new reader-facing entry for the change being prepared, not rewritten history.
Expand All @@ -31,7 +44,7 @@ func changelogEntries(value []byte) (map[string]int, error) {
for _, rawLine := range strings.Split(strings.ReplaceAll(string(value), "\r\n", "\n"), "\n") {
line := strings.TrimSpace(rawLine)
if strings.HasPrefix(line, "## ") {
if line == "## Unreleased" {
if isUnreleasedHeading(line) {
if foundUnreleased {
return nil, fmt.Errorf("%s must contain exactly one ## Unreleased section", changelogPath)
}
Expand Down
22 changes: 20 additions & 2 deletions boatstack/changelog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@ func writeChangelog(t *testing.T, repo, value string) {
}
}

func TestUnreleasedHeadingCompatibility(t *testing.T) {
for _, test := range []struct {
heading string
want bool
}{
{heading: "## Unreleased", want: true},
{heading: "## [Unreleased]", want: true},
{heading: "## [Unreleased] - 2026-07-19", want: true},
{heading: "## [Unreleased] - 2026-7-19", want: false},
{heading: "## [Unreleased] - not-a-date", want: false},
{heading: "## [1.0.0] - 2026-07-19", want: false},
} {
if got := isUnreleasedHeading(test.heading); got != test.want {
t.Errorf("isUnreleasedHeading(%q) = %v, want %v", test.heading, got, test.want)
}
}
}

func TestChangelogPolicyAcceptsExistingAndFirstEntries(t *testing.T) {
for _, test := range []struct {
name string
Expand All @@ -32,8 +50,8 @@ func TestChangelogPolicyAcceptsExistingAndFirstEntries(t *testing.T) {
head: "# Changelog\n\n## Unreleased\n\n### Added\n\n- Existing capability.\n- New reader-visible capability.\n\n## 1.0.0\n\n- First release.\n",
},
{
name: "first changelog",
head: "# Changelog\n\n## Unreleased\n\n### Maintenance\n\n- Document the supported delivery workflow.\n",
name: "first dated changelog",
head: "# Changelog\n\n## [Unreleased] - 2026-07-19\n\n### Maintenance\n\n- Document the supported delivery workflow.\n",
},
{
name: "existing legacy changelog adopts policy",
Expand Down
Loading
Loading