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/b37bfc311634ab20082ce1d768f0c953530d72cc/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/c99b5f302c89934cc18926e19c7f49eb5e6e01cf/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
22 changes: 14 additions & 8 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
},
"files": {
".gitignore": "a7079e923a776f14f1bb3a6aa0a11a133a8e1dfb35af020f327623357b7e3957",
"CONTRIBUTING.md": "dde868caa9c38db8ceaa264ee034fdd33eb413e57b570845dfeb5d44c1e9d8d7",
"CONTRIBUTING.md": "d751bf5c5f125b5909a23fad896a4b4700370a9bf5659b15020bfc0a2da8f20d",
"README.md": "3ce3e95e511089b44e946a44b8d5f4f81d019ece5336db65b2cab1f9dc4d4dad",
"assets/boatstack-journey.svg": "e465befc50c8ce30f3e07e8fd97012931beeb053392c8fbf38ad645023b3cc63",
"assets/boatstack-mark.svg": "be1f984da1bfa69fa5d1f986d8343d21f7e20921b71db888c928b4d2e54b09b5",
"assets/boatstack-portability.svg": "66dfdfa85db857b3bd18b32047a6975f1fbbfc4dc091158e8277193f9969a346",
"boatstack/AGENTS.md": "bc76221e1fe90a91afbacd7c6bc9b41a70e6c10fc128c275a6a0b9bc094d9506",
"boatstack/BUG-worktree-delivery-state.md": "02469cf51c3849dad5743783e248e5c04583e4240507fbef0e3f890cd6a95724",
"boatstack/SKILL.md": "4b25970267c3ce508bf2dc8cf6d25e443a6ea6a7b905c170e5dc081ff2fb2be3",
"boatstack/SKILL.md": "634be319004241bf6f6c6674b20350923e7fb7b6f8329ff0c1e31dabfd7df623",
"boatstack/activation.go": "deb7712d20aed37371254596613bfaccfc05fcb59634408b03378d1a0bf693ea",
"boatstack/agents/gemini.yaml": "cbf43b387399e456fa6178f86d83e6e35567e6142ff800f8de6ffca306fa963e",
"boatstack/agents/openai.yaml": "68a30a60859556c5a26e16d184594ca243a6043d99c8cf7d66b5dd6d50a93cd1",
Expand All @@ -42,10 +42,11 @@
"boatstack/capture_test.go": "63fa1177738081f1e862364d7a4257f5e259f8e9c36276ba1775b8085b277105",
"boatstack/changelog.go": "6b06be7cd9738de29ba6e87aa2569f3b027a2e618b04524f5abd7abaa17945bf",
"boatstack/changelog_test.go": "ce792f23a7fe1e09fb3096cd1314130a6ab69321d4877b12a8e994027541baf7",
"boatstack/cmd/boatstack-helper/coverage_conformance_test.go": "347810fec8cc65300ad58cf84570040a001f6dffd9d464f21038034bec6f00e9",
"boatstack/cmd/boatstack-helper/coverage_conformance_test.go": "f5a931d2b5cdb0d32af85acbbd499fa0f509ed36d62e772e6b6072e348200aca",
"boatstack/cmd/boatstack-helper/flow.go": "0d41c7a86b49004e897f59551780220841d5941396202c81de97a4d52f593520",
"boatstack/cmd/boatstack-helper/main.go": "9e0712b0a3936a3066a33b9c97f92d8ebc07f6e200664b21e6a3af03a00d9f3b",
"boatstack/cmd/boatstack-helper/main.go": "81f10044f507a6d62646a2f0220595323823ab60f4921c2a0e925b88b0f2301a",
"boatstack/cmd/boatstack-helper/main_test.go": "b36c52d6d5c9dd2428730de10ff18194b7e32a98722e41341c301c6f7a04cad5",
"boatstack/cmd/boatstack-helper/retro.go": "68b83e33ade5b5fec126c70ec798fdcbed22fda755dc1cad2758143fead8e187",
"boatstack/command.go": "4726ac515dedab4947be7eb48f88c6cb8b53d674124504b69f03e6396b080ee8",
"boatstack/command_test.go": "9f707abba3640add81c3e97ba7e72fedbf98f3394b1c060a9ca4b4a28e919968",
"boatstack/compiled_artifact_resolution_test.go": "0748d67643263e698211eb04d46464e1dd3db15d94537f5fd5092b5aa689745b",
Expand Down Expand Up @@ -130,8 +131,10 @@
"boatstack/internal/deliverycontrol/trajectorylog_test.go": "227dd6ed9ce181d517a37b67ef4d64dd93779a533eae804798ab54de35c7f13e",
"boatstack/internal/deliverycontrol/transition.go": "b43abb0e99d29697b27b0bb8ee2e2f5f31f3471a2983f25d18ae3564ee246775",
"boatstack/internal/retromine/adapters.go": "7736ebe7fe200dc6aca2799dab964a49031e4f840a049dc872f0bb62053a1ed8",
"boatstack/internal/retromine/classify.go": "060404bfbea3ccc7115dedea5af9c02e53f92c4042a027e68c17042ca996a2cf",
"boatstack/internal/retromine/cluster.go": "ddb6b3506ac192ab37f0341e57437ddf365207c42163da423a06bbfd856758f5",
"boatstack/internal/retromine/event.go": "4d41cbf37209d01bc5cc083d144f835950700aa7f6e39d0661b525b5e045227a",
"boatstack/internal/retromine/report.go": "60a2ecc1416d4b6d71e359aafba7e2a8a80b12dd20d733dddcd90fcd691a9155",
"boatstack/internal/retromine/retromine_conformance_test.go": "33883893db1e455901d9c0e93767c39b475fae728aa9ee9dd925a2c21e5b0ec4",
"boatstack/internal/retromine/testdata/session-alpha.jsonl": "45ed8fa691af9db3f957e87175dbbeffd8fdd2f001324e86a5fd68e29ffbe244",
"boatstack/internal/retromine/testdata/session-beta.txt": "8e85e6a4442e3c892166df97ec879d998b3a47bdad5ef2bf785674b3149c36c2",
Expand Down Expand Up @@ -184,6 +187,8 @@
"boatstack/release.go": "82dcb4ca59e8c79a68d5333d650f90e64abd448d04e0c6f504fdf07f42b5ed76",
"boatstack/release_test.go": "5cf2d76fe9b836a91ca68eba53d5585e2c4be5b9421aaf939ea0723063a24690",
"boatstack/repair_state_test.go": "f3779ac47c3db3927175a545728d3b2e020dbc85f41394d8235753b52afc3739",
"boatstack/retro.go": "8a6f13b948574c90d0f06c3b9f5570d08931e66a4c78dbcc208da8c696c2a42b",
"boatstack/retro_conformance_test.go": "827250d2fc49717fb5e58a4cf79e1d5c489c37574d8a2cf9348fa1cd8c328713",
"boatstack/run.go": "3127e8c054b80e41413011e423ba9463fe06a7e6c9e1e71750ab98587871d89d",
"boatstack/run_test.go": "5b291510fa90cefdc26eb89e18a3443385456a6ebc73408325ac1945b7c084d6",
"boatstack/runtime.go": "368bb43a0e3042bde2d4bab1b62df560a93f5928384c7c8f5e27e8a836628af4",
Expand Down Expand Up @@ -221,10 +226,10 @@
"docs/benchmark-corpus-audit.md": "f2d206fe8579a514f9da82b2c96c19b343ac004be67617e1bd34f0f8e0e5e6c6",
"docs/benchmark-submission-audit.md": "9518abdd17690729c6423f87cab20418ed47b0915b5faa44b9ef975e9e9c3b79",
"docs/configuration.md": "221f979506a3a9de357e5277f1329c345bf175346ec8dfc8fdd1212fb100dea1",
"docs/evidence-engineered-coding.md": "2a2b905e044d89cad2855f10c45261e980b0507c60e275165c8de8ed2a663f42",
"docs/evidence-engineered-coding.md": "9e343b431d4fa013c094b252fb47eadcf06810f11c730538f1cafb19c0d802b7",
"docs/generated-files.md": "437791765b0a4015032ae21d1a6618563cad92b7402819e4f963bf5ae16284a3",
"docs/getting-started.md": "51c2823f21e35140d31e6d5083dc4b89fddd24721ac6acc474154a4da53ee9f8",
"docs/public-claims.json": "6f78d9f8b0ce76079f23a43d84008045dc7e51e5004a2f3e394784e44a3b752e",
"docs/public-claims.json": "b843e27a0ff7ce283a8ca5cdf7e54244c98dd551e9b35c0c9757845907dc6dd3",
"docs/public-surface.md": "713f7a050b5f339cf948299103ef3800417dccfecf2cc1a4166397ea6f978907",
"docs/research-and-design.md": "8d78678108f0a6c924e1ff9b32c0f81aae9d1f779e0082843b6f99ad993ae2b6",
"docs/safety.md": "7b9b5c515d36e683767ec8d3d9d6d119ac93650b2f629d351deadd4c600ed6a6",
Expand All @@ -238,7 +243,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": "2c2182f842a9ab47814c685ad0762bb63da4859fc27f583a25a32597e97d7b79",
"labs/diagram-json/plan.lock.json": "356bd3d6143508b68469cd651410e773f32614f71f5eaf906aa47f3921428047",
"labs/diagram-json/plan.md": "3cc4f533b8d69386deff16b3a594a3ba09d4c0c3db636cccd8c4380084ce6a51",
"labs/diagram-json/questions.md": "74733b015002c8a6777c558e7e997fa48c94850b9bd39054fe9366c97ecf728d",
"labs/diagram-json/request.md": "0808fc41c36779c404f4a3a121167da6e76cac56df526e70f9ed6d3e0d4c02ed",
Expand Down Expand Up @@ -377,12 +382,13 @@
"release-notes/2026-07-28-post-publish-prescriptions.md": "a0b728df569bdba40e3b6107179b35873bad00e1ed1c2574e7eecf438b6962f4",
"release-notes/2026-07-28-pr-phase-observation.md": "8c5615013eb88ce9561d30897e47f6fa967e0157c4c245f0c35a1f31e4e132e2",
"release-notes/2026-07-28-protected-native-auto-merge.md": "67dc76a6e7ce51034a0eadc541ba7a8946cfcabe5433cedc25db55321dfb8b62",
"release-notes/2026-07-28-retro-derive-proposals.md": "c90797d76fb00816340475620b584ad150fa32d7d10bc14997a614529ef9b558",
"release-notes/2026-07-28-retromine-recurrence-detector.md": "27790993a02e73f3a2700dce7340d044aeb0e52f785ded68271ff6673add9e25"
},
"generator": "operatorstack/intelligence-flow:boatstack-distribution",
"schema_version": 1,
"source": {
"commit": "b37bfc311634ab20082ce1d768f0c953530d72cc",
"commit": "c99b5f302c89934cc18926e19c7f49eb5e6e01cf",
"path": "labs/12-product-engineering-loop",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
2 changes: 2 additions & 0 deletions boatstack/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ This is a two-slice ZCA projection: the reviewer brief minimizes review effort,

Read [failure-moves.md](references/failure-moves.md) before proposing a loop change.

For a retro over past sessions, run the read-only `.product-loop/bin/boatstack-helper retro derive --input <transcript> [--input <transcript> ...]`. It detects operator instructions that recur across sessions and classifies each as a missing observation, verb, setpoint, or guard, with a suggested typed promotion. It reads only the transcript files the user names, works fully offline, and writes nothing. A recurring instruction is evidence of a missing typed control — promote it by hand through the normal reviewed delivery flow; never turn it into a saved prompt, and never apply a proposal automatically.

1. Classify the observed failure below the surface symptom.
2. State a mechanism and the exact failure population the move targets.
3. Estimate cost, risk, and possible regressions.
Expand Down
4 changes: 4 additions & 0 deletions boatstack/cmd/boatstack-helper/coverage_conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ var nonDeliveryVerbs = map[string]bool{
"workspace-sync": true,
// Flow layer itself is read-only navigation over the machine, not a transition.
"flow": true,
// Retro derivation reads operator-supplied transcripts and proposes typed
// promotions; it mutates nothing, so it registers no delivery transition.
// control-law: retro-proposes-never-enforces
"retro": true,
}

// dispatchVerbs parses main.go and returns the set of command verbs the run()
Expand Down
4 changes: 3 additions & 1 deletion boatstack/cmd/boatstack-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ func workspaceSyncCommand(arguments []string) int {

func run() int {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <attach|detach|detached-status|context|activate|deactivate|init|update|check-update|repair-status|operation-status|prepare-update-pr|publish-update-pr|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|activate-plan|delivery-status|next-status|recovery-status|repair-state|mutation-status|undo|run-preflight|record-change|ignore-delivery|record-delivery-gate|record-pr-visual-evidence|capture-evidence|provision-capability|capability-register|record-pr-visual-publication|check-safety|migrate-config|safety-hook|ambient-safety-hook|diagnose-hook|render-denial|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-reap|workspace-status|workspace-sync|flow|doctor|version>")
fmt.Fprintln(os.Stderr, "usage: boatstack-helper <attach|detach|detached-status|context|activate|deactivate|init|update|check-update|repair-status|operation-status|prepare-update-pr|publish-update-pr|release-classify|next-patch|export|check-source-plan|planning-write|check-plan|record-approval|activate-plan|delivery-status|next-status|recovery-status|repair-state|mutation-status|undo|run-preflight|record-change|ignore-delivery|record-delivery-gate|record-pr-visual-evidence|capture-evidence|provision-capability|capability-register|record-pr-visual-publication|check-safety|migrate-config|safety-hook|ambient-safety-hook|diagnose-hook|render-denial|pr-context|check-pr|publish-pr|workspace-cut|workspace-cleanup|workspace-reap|workspace-status|workspace-sync|flow|retro|doctor|version>")
return 2
}
switch os.Args[1] {
Expand Down Expand Up @@ -1553,6 +1553,8 @@ func run() int {
return migrateConfigCommand(os.Args[2:])
case "flow":
return flowCommand(os.Args[2:])
case "retro":
return retroCommand(os.Args[2:])
case "version":
fmt.Printf("Boatstack %s (%s)\n", boatstack.Version, boatstack.SourceCommit)
return 0
Expand Down
66 changes: 66 additions & 0 deletions boatstack/cmd/boatstack-helper/retro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"flag"
"fmt"
"os"

boatstack "github.com/operatorstack/boatstack/boatstack"
)

// retroCommand is the derive-only entry point for transcript mining. The CLI
// boundary owns the ONLY I/O in the pipeline: it reads the operator-supplied
// paths and prints the report to stdout. Below this boundary the derivation
// is capability-free (no filesystem, network, subprocess, or clock), and
// nothing anywhere in the pipeline writes, mutates state, or runs a command.
// control-law: retro-proposes-never-enforces
func retroCommand(arguments []string) int {
if len(arguments) == 0 || arguments[0] != "derive" {
fmt.Fprintln(os.Stderr, "usage: boatstack-helper retro derive --input <transcript> [--input <transcript> ...] [--format events|claudecode|plaintext] [--json]")
return 2
}
flags := flag.NewFlagSet("retro derive", flag.ContinueOnError)
var inputs stringList
flags.Var(&inputs, "input", "transcript file to mine (repeatable)")
format := flags.String("format", "", "transcript format: events, claudecode, or plaintext (default: sniff per file)")
jsonOutput := flags.Bool("json", false, "print the structured derivation report")
if err := flags.Parse(arguments[1:]); err != nil {
return 2
}
inputs = append(inputs, flags.Args()...)
if len(inputs) == 0 {
fmt.Fprintln(os.Stderr, "retro derive requires at least one --input transcript; Boatstack never scans for transcripts on its own")
return 2
}
loaded := make([]boatstack.RetroInput, 0, len(inputs))
for _, path := range inputs {
content, err := os.ReadFile(path)
if err != nil {
return fail(err)
}
loaded = append(loaded, boatstack.RetroInput{Name: path, Content: content})
}
report, err := boatstack.RetroDerive(*format, loaded)
if err != nil {
return fail(err)
}
if *jsonOutput {
value, marshalErr := boatstack.MarshalJSON(report)
if marshalErr != nil {
return fail(marshalErr)
}
fmt.Print(string(value))
} else {
fmt.Print(boatstack.FormatRetroReport(report))
}
return 0
}

// stringList is a repeatable string flag.
type stringList []string

func (s *stringList) String() string { return fmt.Sprint([]string(*s)) }
func (s *stringList) Set(value string) error {
*s = append(*s, value)
return nil
}
94 changes: 94 additions & 0 deletions boatstack/internal/retromine/classify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package retromine

import "strings"

// Gap classification names WHICH typed construct a recurring instruction is
// compensating for. The four gap types are the four ways a controller can be
// missing a term:
//
// missing_observation — the operator keeps asking what the system could show
// missing_verb — the operator keeps describing an action to take
// missing_setpoint — the operator keeps restating a goal or condition to
// pursue ("until", "every time", "at least")
// missing_guard — the operator keeps warning what must not happen
//
// The classifier is a deterministic keyword lexicon over the normalized
// instruction, with fixed precedence guard > setpoint > observation > verb:
// a guard misclassified as a verb could become an action proposal, so the
// constraining readings win. Anything the lexicon cannot place lands in
// unclassified, which is REPORTED but never generates a proposal.
// control-law: retro-proposes-never-enforces
const (
GapObservation = "missing_observation"
GapVerb = "missing_verb"
GapSetpoint = "missing_setpoint"
GapGuard = "missing_guard"
GapUnclassified = "unclassified"
)

// The lexicons match either whole tokens or normalized phrases. Normalization
// has already lowered the text and stripped punctuation ("don't" → "don t").
var (
guardPhrases = []string{"don t", "do not", "make sure not", "must not", "never", "only if", "unless", "be careful", "avoid", "without asking", "instead of"}
guardTokens = []string{"dont", "stop"}

setpointPhrases = []string{"until", "at least", "at most", "within", "every time", "each time", "whenever", "keep doing", "always", "from now on", "before you finish", "when green", "when it passes"}

observationPhrases = []string{"check the", "check whether", "check if", "what is the", "what s the", "show me", "look at", "status of", "is it", "did it", "how is", "where is", "monitor"}

verbTokens = []string{"run", "merge", "publish", "push", "rerun", "retry", "open", "record", "fix", "update", "deploy", "rebase", "commit", "create", "install", "sync", "clean", "make"}
)

// ClassifyGap places one normalized instruction into a gap type.
func ClassifyGap(normalized string) string {
padded := " " + normalized + " "
containsPhrase := func(phrases []string) bool {
for _, phrase := range phrases {
if strings.Contains(padded, " "+phrase+" ") {
return true
}
}
return false
}
tokens := map[string]bool{}
for _, token := range strings.Fields(normalized) {
tokens[token] = true
}
containsToken := func(list []string) bool {
for _, token := range list {
if tokens[token] {
return true
}
}
return false
}
switch {
case containsPhrase(guardPhrases) || containsToken(guardTokens):
return GapGuard
case containsPhrase(setpointPhrases):
return GapSetpoint
case containsPhrase(observationPhrases):
return GapObservation
case containsToken(verbTokens):
return GapVerb
default:
return GapUnclassified
}
}

// SuggestedShape names the typed construct to add for a gap type — prose
// pointing a human at the right kind of promotion, never a diff.
func SuggestedShape(gapType string) string {
switch gapType {
case GapObservation:
return "Add a typed observation: a read-only status or frontier field that answers this without being asked."
case GapVerb:
return "Add or prescribe a typed verb: a deterministic command the flow names at the right state."
case GapSetpoint:
return "Add a typed setpoint: a persisted goal or condition (like delivery.terminal) the flow pursues so this stops being restated."
case GapGuard:
return "Add a typed guard: an enforced precondition or denial (a gate or policy) instead of a remembered warning."
default:
return ""
}
}
Loading
Loading