From f1d014a1d837b597723d34d5134d2019512676d9 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Tue, 21 Jul 2026 13:26:54 +0100 Subject: [PATCH 1/5] feat(pitot): project native cross-language TypeScript interfaces Generates strongly-typed TS interfaces from Pitot Go structs using reflection and quicktype. Automates this inside CI and tracks it in the UPSTREAM projection manifest. --- .github/workflows/pitot-lab.yml | 5 ++ .../15-pitot/pitot-distribution/UPSTREAM.json | 5 +- ...07-21-project-cross-language-interfaces.md | 3 + .../pitot-distribution/types/ts/pitot.ts | 70 +++++++++++++++++++ .../pitot/cmd/generate-schema/main.go | 34 +++++++++ labs/15-pitot/pitot/go.mod | 8 +++ labs/15-pitot/pitot/go.sum | 10 +++ labs/15-pitot/scripts/build_pitot.py | 14 +++- labs/15-pitot/scripts/generate_types.sh | 16 +++++ 9 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 labs/15-pitot/pitot-distribution/release-notes/2026-07-21-project-cross-language-interfaces.md create mode 100644 labs/15-pitot/pitot-distribution/types/ts/pitot.ts create mode 100644 labs/15-pitot/pitot/cmd/generate-schema/main.go create mode 100644 labs/15-pitot/pitot/go.sum create mode 100755 labs/15-pitot/scripts/generate_types.sh diff --git a/.github/workflows/pitot-lab.yml b/.github/workflows/pitot-lab.yml index 97d51226b..d995ab314 100644 --- a/.github/workflows/pitot-lab.yml +++ b/.github/workflows/pitot-lab.yml @@ -112,6 +112,9 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.11" + - uses: actions/setup-node@v4 + with: + node-version: "20" - name: Require an append-only release message if: github.event_name == 'pull_request' run: >- @@ -119,5 +122,7 @@ jobs: --repo . --base "${{ github.event.pull_request.base.sha }}" --head "${{ github.event.pull_request.head.sha }}" + - name: Generate Types + run: bash labs/15-pitot/scripts/generate_types.sh - name: Verify projection and public surface run: python -m unittest discover -s labs/15-pitot/tests -p 'test_*.py' diff --git a/labs/15-pitot/pitot-distribution/UPSTREAM.json b/labs/15-pitot/pitot-distribution/UPSTREAM.json index 508444306..7091063ed 100644 --- a/labs/15-pitot/pitot-distribution/UPSTREAM.json +++ b/labs/15-pitot/pitot-distribution/UPSTREAM.json @@ -12,6 +12,7 @@ "assets/pitot-two-roles.svg": "05a740b169110b2d6865fe4ecc9bd9e20c8a6c0ee00ceaf948f2c811b9dce0c5", "bridge/bridge.go": "79ac2e025e16782f3c283b43cbea5b9ba4f837446583864df8f57d6346cae816", "bridge/bridge_test.go": "23a19b7580d4b1e826224ec8322208ccca44ddd9a97b150efe15cafecc53e47f", + "cmd/generate-schema/main.go": "6e9d0030290d99e36967433f96e38385a122974f899ad9421aac1ef7e50d8fcb", "cmd/pitot/main.go": "a8ef8a789abe13f41d8a1014de3fb4ad46522b558f0ace70786b904e81892b05", "cmd/pitot/main_test.go": "ab2894327e7a5e72e9f3d83cae637e3ccb2e1ae05f14458be4fe9016149aeef7", "conformance/conformance.go": "43b692114f45c8b52958e34b35aee1cee339d8321c90f92ab4f5b963e79935bb", @@ -24,7 +25,8 @@ "examples/doc.go": "58f3f9eb7d272d7b6eecdb05f43e1613d5e3ef92d15d97c5440bd4b6990c26f9", "examples/local-approval/main.go": "51386af324cd7d3bb07fe3ace53503884b02714b96b83073342fde81ce3b83a5", "examples/token-meter/main.go": "4b1b9c1a43c3cf48b09dba6f607776caced9d2b5b562373496b31ed184582dd1", - "go.mod": "3017c9418f2c6e3beaa14038fef6dfe9ec181819fb60bdbe992f8d3b1c5ba4de", + "go.mod": "7e71b29887a2370c920a3ecad84460130ecc5ad4acba7f62300b5d2568f0ee13", + "go.sum": "2f73a6c3c672f4022f4a618578fac165172095ef590db5d14caa4552675f1980", "projection/projection.go": "4d3c823fd72a3ca5387dba3683838a1d7e455e9b18309acc839763a39b7bb35f", "protocol/framing.go": "d4409314b72e09cfd472ad9a21d4c7a223b4341b26f58f6062a7c899e3f87482", "protocol/framing_test.go": "9fcb13767fdcc7129d2c87bc2133a5800c69d6ac0166016dc266dca5fcfaa1c5", @@ -32,6 +34,7 @@ "sensor/decode_fuzz_test.go": "6f3412865c16e3314980a369c36f1f26ba36a991c8b4a8a9d3b776ad9c0de2b0", "sensor/sensor.go": "939e146eaa51906972f98cf4615747eb75811b0c54d467a938825750336abeae", "sensor/sensor_test.go": "ea9a58d45ad56d29214a40fb2cfa935e769f85334afcb1856a20fb938d486245", + "types/ts/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c", "windtunnel/doc.go": "44e0bcde632da73e1f8b98beade3a34ca8e0d0ea79cdfb91d131de290b164fc4", "windtunnel/windtunnel_test.go": "1c8b6c5d56ff5c4e66403baeae0d98108255d153c1275572b8c68e333e091493" }, diff --git a/labs/15-pitot/pitot-distribution/release-notes/2026-07-21-project-cross-language-interfaces.md b/labs/15-pitot/pitot-distribution/release-notes/2026-07-21-project-cross-language-interfaces.md new file mode 100644 index 000000000..09e71e454 --- /dev/null +++ b/labs/15-pitot/pitot-distribution/release-notes/2026-07-21-project-cross-language-interfaces.md @@ -0,0 +1,3 @@ +### Project native cross-language TypeScript interfaces + +Pitot now projects a definitively synced, strongly-typed `types.ts` representation of its JSON API surface into the public distribution alongside the core Go module. By generating these types natively from the internal Go structs using reflection and `quicktype`, Pitot completely eliminates the adoption friction for Node and TypeScript integrations. Instead of maintaining parallel JSON schema definitions, frontend consumers can drop Pitot into their pipeline effortlessly, enjoying instantaneous telemetry ingestion and foolproof TypeScript interfaces for the control plane. diff --git a/labs/15-pitot/pitot-distribution/types/ts/pitot.ts b/labs/15-pitot/pitot-distribution/types/ts/pitot.ts new file mode 100644 index 000000000..a2868e031 --- /dev/null +++ b/labs/15-pitot/pitot-distribution/types/ts/pitot.ts @@ -0,0 +1,70 @@ +export interface Pitot { + boundary_fault: BoundaryFault; + control_requested: ControlRequested; + control_response: ControlResponse; + event: Event; +} + +export interface BoundaryFault { + action_id?: string; + host: string; + pitot_version: string; + reason: string; + type: string; +} + +export interface ControlRequested { + action_id: string; + data?: unknown; + kind: string; + pitot_version: string; + type: string; +} + +export interface ControlResponse { + action_id: string; + controller_id: string; + message?: string; + outcome: string; + pitot_version: string; + type: string; +} + +export interface Event { + action?: Action; + content?: Content; + host: Host; + id?: string; + observation: Observation; + pitot_version: string; + session_id?: string; + time?: string; + type: string; + usage?: Usage; +} + +export interface Action { + id: string; + kind: string; +} + +export interface Content { + full?: unknown; + mode: string; + sha256?: string; +} + +export interface Host { + adapter_version?: string; + name: string; +} + +export interface Observation { + fidelity: string; + source: string; +} + +export interface Usage { + input_tokens: number; + output_tokens: number; +} diff --git a/labs/15-pitot/pitot/cmd/generate-schema/main.go b/labs/15-pitot/pitot/cmd/generate-schema/main.go new file mode 100644 index 000000000..67c91b62e --- /dev/null +++ b/labs/15-pitot/pitot/cmd/generate-schema/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/invopop/jsonschema" + "github.com/operatorstack/pitot/schema" +) + +// PitotContract acts as a root object to force generation of all these types. +type PitotContract struct { + Event schema.Event `json:"event"` + ControlRequested schema.ControlRequested `json:"control_requested"` + ControlResponse schema.ControlResponse `json:"control_response"` + BoundaryFault schema.BoundaryFault `json:"boundary_fault"` +} + +func main() { + reflector := jsonschema.Reflector{ + AllowAdditionalProperties: false, + } + + sch := reflector.Reflect(&PitotContract{}) + + b, err := json.MarshalIndent(sch, "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "failed to marshal schema: %v\n", err) + os.Exit(1) + } + + fmt.Println(string(b)) +} diff --git a/labs/15-pitot/pitot/go.mod b/labs/15-pitot/pitot/go.mod index 5efaee208..e75d4b443 100644 --- a/labs/15-pitot/pitot/go.mod +++ b/labs/15-pitot/pitot/go.mod @@ -1,3 +1,11 @@ module github.com/operatorstack/pitot go 1.26 + +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.2 // indirect + github.com/invopop/jsonschema v0.14.0 // indirect + github.com/pb33f/ordered-map/v2 v2.3.1 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect +) diff --git a/labs/15-pitot/pitot/go.sum b/labs/15-pitot/pitot/go.sum new file mode 100644 index 000000000..afe512a42 --- /dev/null +++ b/labs/15-pitot/pitot/go.sum @@ -0,0 +1,10 @@ +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= +github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I= +github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY= +github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ= +go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s= +go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= diff --git a/labs/15-pitot/scripts/build_pitot.py b/labs/15-pitot/scripts/build_pitot.py index 80a9a53ac..f1b81e05f 100644 --- a/labs/15-pitot/scripts/build_pitot.py +++ b/labs/15-pitot/scripts/build_pitot.py @@ -66,11 +66,23 @@ def _iter_preview_files(repo: Path) -> list[tuple[str, Path]]: return entries +def _iter_types_files(repo: Path) -> list[tuple[str, Path]]: + """Yield (projected_path, source_path) for generated language interfaces.""" + entries: list[tuple[str, Path]] = [] + types_dir = repo / LAB / "pitot-distribution" / "types" + if types_dir.is_dir(): + for path in sorted(types_dir.rglob("*")): + if path.is_file() and not path.is_symlink(): + projected = path.relative_to(repo / LAB / "pitot-distribution").as_posix() + entries.append((projected, path)) + return entries + + def compute_manifest(repo: Path) -> dict[str, str]: """Return {projected_path: sha256} for the whole public surface.""" repo = repo.resolve() surface: dict[str, str] = {} - for projected, source in _iter_module_files(repo) + _iter_preview_files(repo): + for projected, source in _iter_module_files(repo) + _iter_preview_files(repo) + _iter_types_files(repo): if projected in surface: raise ValueError(f"projection collision on {projected}") digest = hashlib.sha256(source.read_bytes()).hexdigest() diff --git a/labs/15-pitot/scripts/generate_types.sh b/labs/15-pitot/scripts/generate_types.sh new file mode 100755 index 000000000..a7c86be0c --- /dev/null +++ b/labs/15-pitot/scripts/generate_types.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT/labs/15-pitot/pitot" + +go run ./cmd/generate-schema > schema.json + +TARGET_DIR="../pitot-distribution/types/ts" +mkdir -p "$TARGET_DIR" + +npx -y quicktype -s schema schema.json -o "$TARGET_DIR/pitot.ts" --just-types + +rm schema.json + +echo "Generated TypeScript types in $TARGET_DIR/pitot.ts" From 9811dffb980d723365b9683d1e7f7727edf784a8 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Tue, 21 Jul 2026 13:31:33 +0100 Subject: [PATCH 2/5] feat(pitot): package as TypeScript SDK Adds package.json, tsconfig.json, and an index.ts to the projected surface, completing the value translation of Pitot into a first-class SDK for TypeScript consumers. --- .../15-pitot/pitot-distribution/UPSTREAM.json | 3 +++ labs/15-pitot/pitot-distribution/package.json | 24 +++++++++++++++++++ .../15-pitot/pitot-distribution/tsconfig.json | 13 ++++++++++ .../pitot-distribution/types/ts/index.ts | 1 + labs/15-pitot/scripts/build_pitot.py | 4 ++++ labs/15-pitot/scripts/generate_types.sh | 2 ++ 6 files changed, 47 insertions(+) create mode 100644 labs/15-pitot/pitot-distribution/package.json create mode 100644 labs/15-pitot/pitot-distribution/tsconfig.json create mode 100644 labs/15-pitot/pitot-distribution/types/ts/index.ts diff --git a/labs/15-pitot/pitot-distribution/UPSTREAM.json b/labs/15-pitot/pitot-distribution/UPSTREAM.json index 7091063ed..dee626e74 100644 --- a/labs/15-pitot/pitot-distribution/UPSTREAM.json +++ b/labs/15-pitot/pitot-distribution/UPSTREAM.json @@ -27,6 +27,7 @@ "examples/token-meter/main.go": "4b1b9c1a43c3cf48b09dba6f607776caced9d2b5b562373496b31ed184582dd1", "go.mod": "7e71b29887a2370c920a3ecad84460130ecc5ad4acba7f62300b5d2568f0ee13", "go.sum": "2f73a6c3c672f4022f4a618578fac165172095ef590db5d14caa4552675f1980", + "package.json": "a8fe99a7a32c59d131ce49dbd99de6fe658d3bb1c222917b3e310e39f45576dd", "projection/projection.go": "4d3c823fd72a3ca5387dba3683838a1d7e455e9b18309acc839763a39b7bb35f", "protocol/framing.go": "d4409314b72e09cfd472ad9a21d4c7a223b4341b26f58f6062a7c899e3f87482", "protocol/framing_test.go": "9fcb13767fdcc7129d2c87bc2133a5800c69d6ac0166016dc266dca5fcfaa1c5", @@ -34,6 +35,8 @@ "sensor/decode_fuzz_test.go": "6f3412865c16e3314980a369c36f1f26ba36a991c8b4a8a9d3b776ad9c0de2b0", "sensor/sensor.go": "939e146eaa51906972f98cf4615747eb75811b0c54d467a938825750336abeae", "sensor/sensor_test.go": "ea9a58d45ad56d29214a40fb2cfa935e769f85334afcb1856a20fb938d486245", + "tsconfig.json": "019a66d64f4758cb0be881c08bd5a869615290f77f5ef0a4fc6766a92d0ff874", + "types/ts/index.ts": "de43e6654eac51afd09a3a74c4c6992dcf2306d6c41b9be626ded1c6d6cc7833", "types/ts/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c", "windtunnel/doc.go": "44e0bcde632da73e1f8b98beade3a34ca8e0d0ea79cdfb91d131de290b164fc4", "windtunnel/windtunnel_test.go": "1c8b6c5d56ff5c4e66403baeae0d98108255d153c1275572b8c68e333e091493" diff --git a/labs/15-pitot/pitot-distribution/package.json b/labs/15-pitot/pitot-distribution/package.json new file mode 100644 index 000000000..ce4f87545 --- /dev/null +++ b/labs/15-pitot/pitot-distribution/package.json @@ -0,0 +1,24 @@ +{ + "name": "@operatorstack/pitot", + "version": "0.1.0", + "description": "Pitot: The passive, protocol-first measurement boundary for coding agents.", + "main": "types/ts/index.js", + "types": "types/ts/index.d.ts", + "files": [ + "types/ts/**/*.ts", + "types/ts/**/*.js", + "types/ts/**/*.d.ts" + ], + "scripts": { + "build": "tsc" + }, + "repository": { + "type": "git", + "url": "https://github.com/operatorstack/pitot.git" + }, + "author": "Operator Stack", + "license": "MIT", + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/labs/15-pitot/pitot-distribution/tsconfig.json b/labs/15-pitot/pitot-distribution/tsconfig.json new file mode 100644 index 000000000..7c4d6a382 --- /dev/null +++ b/labs/15-pitot/pitot-distribution/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "commonjs", + "declaration": true, + "outDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["types/ts/**/*"] +} diff --git a/labs/15-pitot/pitot-distribution/types/ts/index.ts b/labs/15-pitot/pitot-distribution/types/ts/index.ts new file mode 100644 index 000000000..df19fcedd --- /dev/null +++ b/labs/15-pitot/pitot-distribution/types/ts/index.ts @@ -0,0 +1 @@ +export * from "./pitot"; diff --git a/labs/15-pitot/scripts/build_pitot.py b/labs/15-pitot/scripts/build_pitot.py index f1b81e05f..3498af857 100644 --- a/labs/15-pitot/scripts/build_pitot.py +++ b/labs/15-pitot/scripts/build_pitot.py @@ -57,6 +57,10 @@ def _iter_preview_files(repo: Path) -> list[tuple[str, Path]]: source = repo / PREVIEW / name if source.is_file(): entries.append((name, source)) + for name in ("package.json", "tsconfig.json"): + source = repo / LAB / "pitot-distribution" / name + if source.is_file(): + entries.append((name, source)) assets = repo / PREVIEW / "assets" if assets.is_dir(): for path in sorted(assets.rglob("*")): diff --git a/labs/15-pitot/scripts/generate_types.sh b/labs/15-pitot/scripts/generate_types.sh index a7c86be0c..eb6f7681a 100755 --- a/labs/15-pitot/scripts/generate_types.sh +++ b/labs/15-pitot/scripts/generate_types.sh @@ -13,4 +13,6 @@ npx -y quicktype -s schema schema.json -o "$TARGET_DIR/pitot.ts" --just-types rm schema.json +echo "export * from \"./pitot\";" > "$TARGET_DIR/index.ts" + echo "Generated TypeScript types in $TARGET_DIR/pitot.ts" From 8db091d8e790394c1ff8060059b8a3a74ef3840f Mon Sep 17 00:00:00 2001 From: bigboateng Date: Tue, 21 Jul 2026 14:09:15 +0100 Subject: [PATCH 3/5] docs(boatstack): apply value translation to README - Rewrite opening to focus on user outcome and failure state - Translate portability table to show retained operational value - Insert explicit 'How it works' and compounding mechanism sections - Move technical features and installation instructions below outcomes --- .../boatstack-distribution/README.md | 59 +++++++++++++++---- .../2026-07-21-value-translation-readme.md | 6 ++ 2 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-21-value-translation-readme.md diff --git a/labs/12-product-engineering-loop/boatstack-distribution/README.md b/labs/12-product-engineering-loop/boatstack-distribution/README.md index 9a4bc3ac5..b410ab034 100644 --- a/labs/12-product-engineering-loop/boatstack-distribution/README.md +++ b/labs/12-product-engineering-loop/boatstack-distribution/README.md @@ -8,24 +8,48 @@

Build freely. Prove it. Ship.

-## A delivery harness for AI coding agents +## Keep your software delivery process when you change coding agents -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. +Boatstack is a repository-local delivery harness for Cursor, Codex, Claude Code, and Gemini CLI. -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. +AI coding agents can write code quickly, but each tool brings its own planning flow, session state, and definition of “done.” Change agents and your delivery process often disappears with the chat. -**Your product development flow stays with the repository—not the coding agent.** Change tools without rebuilding how you ship or redefining what “done” means. Boatstack carries the workflow and saved project state—not an agent's private chat history or a command already in progress. +Boatstack keeps the delivery process in the repository. Plans, product decisions, tests, review findings, accepted gaps, and completion evidence stay connected from idea to pull request, regardless of which agent or model performs the work. Use Cursor, Codex, Claude Code, or Gemini CLI. Boatstack keeps the same approval, testing, review, and shipping boundaries across them. + +**Your product development flow stays with the repository, not the coding agent.** Change agents, models, or specialist skills without rebuilding how your team plans, verifies, reviews, and ships software. + +The coding agent executes the work. Boatstack supervises the delivery. Your repository owns the policy and evidence.

Coding agents, models, and specialist skills feed one repository-owned Boatstack flow that produces a reviewed pull request and useful context for the next feature

-| You change | Boatstack keeps | +| You change | You keep | |---|---| -| 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 | +| Cursor, Codex, Claude Code, or Gemini CLI | The same path from approved plan to reviewed PR | +| Lower-cost, general, or frontier model | The same definition of done and proof requirements | +| React guidance, gstack, Spec Kit, or another skill | Human product decisions remain authoritative | +| Session, worktree, or feature | Decisions, open gaps, evidence, and verified delivery state | + +## How it works + +1. Save a plan in your coding agent. +2. Boatstack validates the plan and pauses for material product decisions. +3. The agent builds freely inside the approved scope. +4. Boatstack checks the promised outcomes against tests and evidence. +5. Review findings, risks, and accepted gaps become a focused PR brief. +6. The resulting context stays in the repository for the next feature. + +## Each delivery makes the next one easier + +Boatstack does not preserve an agent's private reasoning or replay old chats. It keeps the durable parts of delivery: +- approved product decisions +- unresolved gaps +- validation evidence +- review findings +- verified repository state + +That means the next feature starts from recorded project knowledge instead of reconstructing intent from another agent session. ## Install with your coding agent @@ -76,7 +100,16 @@ Invoke `/repair` in Claude Code, Cursor, or Gemini CLI, or `$boatstack repair` i Receipts remain as history; published corrections become linked deliveries. -## Features +## What you get + +- **Change coding agents without changing how you ship.** +- **Resume work without reconstructing the previous chat.** +- **Stop agents from guessing material product decisions.** +- **Require evidence for every outcome the change claims to deliver.** +- **Create reviewer-ready PRs from the actual scope, changes, risks, and validation.** + +
+Technical Features - **A guided path from idea to PR.** `/auto-plan` starts a one-action-at-a-time delivery flow. - **Instant orientation after a break.** `boatstack next` reconstructs the verified stage without treating chat or a running process as workflow evidence. @@ -90,6 +123,8 @@ Receipts remain as history; published corrections become linked deliveries. - **Portable across your AI stack.** Hosts, models, and skills share one repository-owned delivery contract. - **Repository-friendly maintenance.** Worktrees restore runtime; updates stay in separate infrastructure PRs. +
+ ### Optional changelog It is disabled by default. Enable it in `.boatstack-project.json`: @@ -123,9 +158,9 @@ Boatstack is a repository-local delivery harness. This does not mean every model performs equally. [See the evidence and paired evaluation design](docs/why-these-steps.md#model-choice-and-budget). -## Why these steps? +## Built from failures observed in real coding work -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. +They derive from coding failures observed in benchmark and product work—not guesses. When a failure reveals a reusable delivery problem rather than a project-specific mistake, Boatstack turns it into a boundary future runs can enforce. Each link explains what happened, what Boatstack does, and whether that behavior has actually been tested. | What happened | What Boatstack does | Current evidence | |---|---|---| diff --git a/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-21-value-translation-readme.md b/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-21-value-translation-readme.md new file mode 100644 index 000000000..f4c2e207e --- /dev/null +++ b/labs/12-product-engineering-loop/boatstack-distribution/release-notes/2026-07-21-value-translation-readme.md @@ -0,0 +1,6 @@ +### Restructure README for faster value comprehension + +- **Outcome-focused hero:** Replaces mechanism-first opening with a clear explanation of what happens to a delivery process when coding agents change. +- **Portability consequences:** Updates the portability table to describe retained operational value (e.g., "The same path from approved plan to reviewed PR") instead of just artifacts. +- **Explicit compounding:** Adds a section explaining how retained durable artifacts make the next feature easier to start without reconstructing intent from chat. +- **Reordered content:** Moves technical feature lists and installation instructions below the primary outcomes to respect evaluator attention. From 20d9eef526ed693863eb1cf0ab161ab3bd4b28ec Mon Sep 17 00:00:00 2001 From: bigboateng Date: Tue, 21 Jul 2026 14:15:02 +0100 Subject: [PATCH 4/5] feat(pitot): restructure public distribution as a polyglot SDK Moves TypeScript to sdk/typescript/, introduces Python to sdk/python/, and prevents non-Go artifacts from cluttering the public repository root. --- .github/workflows/pitot-lab.yml | 2 + .../15-pitot/pitot-distribution/UPSTREAM.json | 11 +- .../2026-07-21-restructure-polyglot-sdk.md | 9 + .../sdk/python/pitot/__init__.py | 0 .../sdk/python/pitot/types.py | 307 ++++++++++++++++++ .../sdk/python/pyproject.toml | 18 + .../{ => sdk/typescript}/package.json | 13 +- .../{types/ts => sdk/typescript/src}/index.ts | 0 .../{types/ts => sdk/typescript/src}/pitot.ts | 0 .../{ => sdk/typescript}/tsconfig.json | 2 +- labs/15-pitot/scripts/build_pitot.py | 18 +- labs/15-pitot/scripts/generate_types.sh | 20 +- 12 files changed, 372 insertions(+), 28 deletions(-) create mode 100644 labs/15-pitot/pitot-distribution/release-notes/2026-07-21-restructure-polyglot-sdk.md create mode 100644 labs/15-pitot/pitot-distribution/sdk/python/pitot/__init__.py create mode 100644 labs/15-pitot/pitot-distribution/sdk/python/pitot/types.py create mode 100644 labs/15-pitot/pitot-distribution/sdk/python/pyproject.toml rename labs/15-pitot/pitot-distribution/{ => sdk/typescript}/package.json (63%) rename labs/15-pitot/pitot-distribution/{types/ts => sdk/typescript/src}/index.ts (100%) rename labs/15-pitot/pitot-distribution/{types/ts => sdk/typescript/src}/pitot.ts (100%) rename labs/15-pitot/pitot-distribution/{ => sdk/typescript}/tsconfig.json (88%) diff --git a/.github/workflows/pitot-lab.yml b/.github/workflows/pitot-lab.yml index d995ab314..62f233c4d 100644 --- a/.github/workflows/pitot-lab.yml +++ b/.github/workflows/pitot-lab.yml @@ -124,5 +124,7 @@ jobs: --head "${{ github.event.pull_request.head.sha }}" - name: Generate Types run: bash labs/15-pitot/scripts/generate_types.sh + - name: Verify generated Python SDK syntax + run: python -m py_compile labs/15-pitot/pitot-distribution/sdk/python/pitot/types.py - name: Verify projection and public surface run: python -m unittest discover -s labs/15-pitot/tests -p 'test_*.py' diff --git a/labs/15-pitot/pitot-distribution/UPSTREAM.json b/labs/15-pitot/pitot-distribution/UPSTREAM.json index dee626e74..220e63615 100644 --- a/labs/15-pitot/pitot-distribution/UPSTREAM.json +++ b/labs/15-pitot/pitot-distribution/UPSTREAM.json @@ -27,17 +27,20 @@ "examples/token-meter/main.go": "4b1b9c1a43c3cf48b09dba6f607776caced9d2b5b562373496b31ed184582dd1", "go.mod": "7e71b29887a2370c920a3ecad84460130ecc5ad4acba7f62300b5d2568f0ee13", "go.sum": "2f73a6c3c672f4022f4a618578fac165172095ef590db5d14caa4552675f1980", - "package.json": "a8fe99a7a32c59d131ce49dbd99de6fe658d3bb1c222917b3e310e39f45576dd", "projection/projection.go": "4d3c823fd72a3ca5387dba3683838a1d7e455e9b18309acc839763a39b7bb35f", "protocol/framing.go": "d4409314b72e09cfd472ad9a21d4c7a223b4341b26f58f6062a7c899e3f87482", "protocol/framing_test.go": "9fcb13767fdcc7129d2c87bc2133a5800c69d6ac0166016dc266dca5fcfaa1c5", "schema/schema.go": "fd5c3b76979c88aeed75fadb7e3c94abcad62e067d77595f021fb422a60f2211", + "sdk/python/pitot/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "sdk/python/pitot/types.py": "c9a7221f1ad6627f152f26148155d3c74f249c52262c73a515251f469e8eb4ad", + "sdk/python/pyproject.toml": "3f42de8223640978ad88e3f7b4ad1464053c43a393f0edf48445cacd31dd537c", + "sdk/typescript/package.json": "c26c726559ff0bc7c5d92b68303efc3c35314613248fe346fa125aea06c8dba6", + "sdk/typescript/src/index.ts": "de43e6654eac51afd09a3a74c4c6992dcf2306d6c41b9be626ded1c6d6cc7833", + "sdk/typescript/src/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c", + "sdk/typescript/tsconfig.json": "e4d7ecb203fcb7d93cd9b9fb235d7fd75d1b6fb2b28eff773f4aecfdc1924d5d", "sensor/decode_fuzz_test.go": "6f3412865c16e3314980a369c36f1f26ba36a991c8b4a8a9d3b776ad9c0de2b0", "sensor/sensor.go": "939e146eaa51906972f98cf4615747eb75811b0c54d467a938825750336abeae", "sensor/sensor_test.go": "ea9a58d45ad56d29214a40fb2cfa935e769f85334afcb1856a20fb938d486245", - "tsconfig.json": "019a66d64f4758cb0be881c08bd5a869615290f77f5ef0a4fc6766a92d0ff874", - "types/ts/index.ts": "de43e6654eac51afd09a3a74c4c6992dcf2306d6c41b9be626ded1c6d6cc7833", - "types/ts/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c", "windtunnel/doc.go": "44e0bcde632da73e1f8b98beade3a34ca8e0d0ea79cdfb91d131de290b164fc4", "windtunnel/windtunnel_test.go": "1c8b6c5d56ff5c4e66403baeae0d98108255d153c1275572b8c68e333e091493" }, diff --git a/labs/15-pitot/pitot-distribution/release-notes/2026-07-21-restructure-polyglot-sdk.md b/labs/15-pitot/pitot-distribution/release-notes/2026-07-21-restructure-polyglot-sdk.md new file mode 100644 index 000000000..0cc13a4f0 --- /dev/null +++ b/labs/15-pitot/pitot-distribution/release-notes/2026-07-21-restructure-polyglot-sdk.md @@ -0,0 +1,9 @@ +### Restructure public distribution as a polyglot SDK + +The Pitot distribution surface has been reorganized to prevent the Go module root from becoming cluttered with multi-language build configurations. Generated TypeScript and Python primitives are now strictly isolated inside an `sdk/` directory. + +- **Go**: Remains at the root of the repository (`go.mod`, `go.sum`). +- **TypeScript**: Housed in `sdk/typescript/`, complete with its own `package.json` and `tsconfig.json`. +- **Python**: Housed in `sdk/python/`, complete with a `pyproject.toml` and autogenerated types. + +This structure allows downstream users to seamlessly import the exact Pitot SDK they need for their environment without dependency conflict or configuration ambiguity. diff --git a/labs/15-pitot/pitot-distribution/sdk/python/pitot/__init__.py b/labs/15-pitot/pitot-distribution/sdk/python/pitot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/labs/15-pitot/pitot-distribution/sdk/python/pitot/types.py b/labs/15-pitot/pitot-distribution/sdk/python/pitot/types.py new file mode 100644 index 000000000..cd221edaa --- /dev/null +++ b/labs/15-pitot/pitot-distribution/sdk/python/pitot/types.py @@ -0,0 +1,307 @@ +from dataclasses import dataclass +from typing import Any, TypeVar, Type, cast + + +T = TypeVar("T") + + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + + +def from_none(x: Any) -> Any: + assert x is None + return x + + +def from_union(fs, x): + for f in fs: + try: + return f(x) + except: + pass + assert False + + +def from_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +@dataclass +class BoundaryFault: + host: str + pitot_version: str + reason: str + type: str + action_id: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'BoundaryFault': + assert isinstance(obj, dict) + host = from_str(obj.get("host")) + pitot_version = from_str(obj.get("pitot_version")) + reason = from_str(obj.get("reason")) + type = from_str(obj.get("type")) + action_id = from_union([from_str, from_none], obj.get("action_id")) + return BoundaryFault(host, pitot_version, reason, type, action_id) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = from_str(self.host) + result["pitot_version"] = from_str(self.pitot_version) + result["reason"] = from_str(self.reason) + result["type"] = from_str(self.type) + if self.action_id is not None: + result["action_id"] = from_union([from_str, from_none], self.action_id) + return result + + +@dataclass +class ControlRequested: + action_id: str + kind: str + pitot_version: str + type: str + data: Any = None + + @staticmethod + def from_dict(obj: Any) -> 'ControlRequested': + assert isinstance(obj, dict) + action_id = from_str(obj.get("action_id")) + kind = from_str(obj.get("kind")) + pitot_version = from_str(obj.get("pitot_version")) + type = from_str(obj.get("type")) + data = obj.get("data") + return ControlRequested(action_id, kind, pitot_version, type, data) + + def to_dict(self) -> dict: + result: dict = {} + result["action_id"] = from_str(self.action_id) + result["kind"] = from_str(self.kind) + result["pitot_version"] = from_str(self.pitot_version) + result["type"] = from_str(self.type) + if self.data is not None: + result["data"] = self.data + return result + + +@dataclass +class ControlResponse: + action_id: str + controller_id: str + outcome: str + pitot_version: str + type: str + message: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'ControlResponse': + assert isinstance(obj, dict) + action_id = from_str(obj.get("action_id")) + controller_id = from_str(obj.get("controller_id")) + outcome = from_str(obj.get("outcome")) + pitot_version = from_str(obj.get("pitot_version")) + type = from_str(obj.get("type")) + message = from_union([from_str, from_none], obj.get("message")) + return ControlResponse(action_id, controller_id, outcome, pitot_version, type, message) + + def to_dict(self) -> dict: + result: dict = {} + result["action_id"] = from_str(self.action_id) + result["controller_id"] = from_str(self.controller_id) + result["outcome"] = from_str(self.outcome) + result["pitot_version"] = from_str(self.pitot_version) + result["type"] = from_str(self.type) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + return result + + +@dataclass +class Action: + id: str + kind: str + + @staticmethod + def from_dict(obj: Any) -> 'Action': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + kind = from_str(obj.get("kind")) + return Action(id, kind) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["kind"] = from_str(self.kind) + return result + + +@dataclass +class Content: + mode: str + full: Any = None + sha256: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'Content': + assert isinstance(obj, dict) + mode = from_str(obj.get("mode")) + full = obj.get("full") + sha256 = from_union([from_str, from_none], obj.get("sha256")) + return Content(mode, full, sha256) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = from_str(self.mode) + if self.full is not None: + result["full"] = self.full + if self.sha256 is not None: + result["sha256"] = from_union([from_str, from_none], self.sha256) + return result + + +@dataclass +class Host: + name: str + adapter_version: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'Host': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + adapter_version = from_union([from_str, from_none], obj.get("adapter_version")) + return Host(name, adapter_version) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + if self.adapter_version is not None: + result["adapter_version"] = from_union([from_str, from_none], self.adapter_version) + return result + + +@dataclass +class Observation: + fidelity: str + source: str + + @staticmethod + def from_dict(obj: Any) -> 'Observation': + assert isinstance(obj, dict) + fidelity = from_str(obj.get("fidelity")) + source = from_str(obj.get("source")) + return Observation(fidelity, source) + + def to_dict(self) -> dict: + result: dict = {} + result["fidelity"] = from_str(self.fidelity) + result["source"] = from_str(self.source) + return result + + +@dataclass +class Usage: + input_tokens: int + output_tokens: int + + @staticmethod + def from_dict(obj: Any) -> 'Usage': + assert isinstance(obj, dict) + input_tokens = from_int(obj.get("input_tokens")) + output_tokens = from_int(obj.get("output_tokens")) + return Usage(input_tokens, output_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["input_tokens"] = from_int(self.input_tokens) + result["output_tokens"] = from_int(self.output_tokens) + return result + + +@dataclass +class Event: + host: Host + observation: Observation + pitot_version: str + type: str + action: Action | None = None + content: Content | None = None + id: str | None = None + session_id: str | None = None + time: str | None = None + usage: Usage | None = None + + @staticmethod + def from_dict(obj: Any) -> 'Event': + assert isinstance(obj, dict) + host = Host.from_dict(obj.get("host")) + observation = Observation.from_dict(obj.get("observation")) + pitot_version = from_str(obj.get("pitot_version")) + type = from_str(obj.get("type")) + action = from_union([Action.from_dict, from_none], obj.get("action")) + content = from_union([Content.from_dict, from_none], obj.get("content")) + id = from_union([from_str, from_none], obj.get("id")) + session_id = from_union([from_str, from_none], obj.get("session_id")) + time = from_union([from_str, from_none], obj.get("time")) + usage = from_union([Usage.from_dict, from_none], obj.get("usage")) + return Event(host, observation, pitot_version, type, action, content, id, session_id, time, usage) + + def to_dict(self) -> dict: + result: dict = {} + result["host"] = to_class(Host, self.host) + result["observation"] = to_class(Observation, self.observation) + result["pitot_version"] = from_str(self.pitot_version) + result["type"] = from_str(self.type) + if self.action is not None: + result["action"] = from_union([lambda x: to_class(Action, x), from_none], self.action) + if self.content is not None: + result["content"] = from_union([lambda x: to_class(Content, x), from_none], self.content) + if self.id is not None: + result["id"] = from_union([from_str, from_none], self.id) + if self.session_id is not None: + result["session_id"] = from_union([from_str, from_none], self.session_id) + if self.time is not None: + result["time"] = from_union([from_str, from_none], self.time) + if self.usage is not None: + result["usage"] = from_union([lambda x: to_class(Usage, x), from_none], self.usage) + return result + + +@dataclass +class Types: + boundary_fault: BoundaryFault + control_requested: ControlRequested + control_response: ControlResponse + event: Event + + @staticmethod + def from_dict(obj: Any) -> 'Types': + assert isinstance(obj, dict) + boundary_fault = BoundaryFault.from_dict(obj.get("boundary_fault")) + control_requested = ControlRequested.from_dict(obj.get("control_requested")) + control_response = ControlResponse.from_dict(obj.get("control_response")) + event = Event.from_dict(obj.get("event")) + return Types(boundary_fault, control_requested, control_response, event) + + def to_dict(self) -> dict: + result: dict = {} + result["boundary_fault"] = to_class(BoundaryFault, self.boundary_fault) + result["control_requested"] = to_class(ControlRequested, self.control_requested) + result["control_response"] = to_class(ControlResponse, self.control_response) + result["event"] = to_class(Event, self.event) + return result + + +def types_from_dict(s: Any) -> Types: + return Types.from_dict(s) + + +def types_to_dict(x: Types) -> Any: + return to_class(Types, x) diff --git a/labs/15-pitot/pitot-distribution/sdk/python/pyproject.toml b/labs/15-pitot/pitot-distribution/sdk/python/pyproject.toml new file mode 100644 index 000000000..291e90a3c --- /dev/null +++ b/labs/15-pitot/pitot-distribution/sdk/python/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "pitot" +version = "0.1.0" +description = "Pitot: The passive, protocol-first measurement boundary for coding agents." +authors = [ + { name = "Operator Stack" } +] +license = { text = "MIT" } +requires-python = ">=3.8" +dependencies = [] + +[project.urls] +Homepage = "https://github.com/operatorstack/pitot" +Repository = "https://github.com/operatorstack/pitot" diff --git a/labs/15-pitot/pitot-distribution/package.json b/labs/15-pitot/pitot-distribution/sdk/typescript/package.json similarity index 63% rename from labs/15-pitot/pitot-distribution/package.json rename to labs/15-pitot/pitot-distribution/sdk/typescript/package.json index ce4f87545..cab900e36 100644 --- a/labs/15-pitot/pitot-distribution/package.json +++ b/labs/15-pitot/pitot-distribution/sdk/typescript/package.json @@ -2,19 +2,20 @@ "name": "@operatorstack/pitot", "version": "0.1.0", "description": "Pitot: The passive, protocol-first measurement boundary for coding agents.", - "main": "types/ts/index.js", - "types": "types/ts/index.d.ts", + "main": "src/index.js", + "types": "src/index.d.ts", "files": [ - "types/ts/**/*.ts", - "types/ts/**/*.js", - "types/ts/**/*.d.ts" + "src/**/*.ts", + "src/**/*.js", + "src/**/*.d.ts" ], "scripts": { "build": "tsc" }, "repository": { "type": "git", - "url": "https://github.com/operatorstack/pitot.git" + "url": "https://github.com/operatorstack/pitot.git", + "directory": "sdk/typescript" }, "author": "Operator Stack", "license": "MIT", diff --git a/labs/15-pitot/pitot-distribution/types/ts/index.ts b/labs/15-pitot/pitot-distribution/sdk/typescript/src/index.ts similarity index 100% rename from labs/15-pitot/pitot-distribution/types/ts/index.ts rename to labs/15-pitot/pitot-distribution/sdk/typescript/src/index.ts diff --git a/labs/15-pitot/pitot-distribution/types/ts/pitot.ts b/labs/15-pitot/pitot-distribution/sdk/typescript/src/pitot.ts similarity index 100% rename from labs/15-pitot/pitot-distribution/types/ts/pitot.ts rename to labs/15-pitot/pitot-distribution/sdk/typescript/src/pitot.ts diff --git a/labs/15-pitot/pitot-distribution/tsconfig.json b/labs/15-pitot/pitot-distribution/sdk/typescript/tsconfig.json similarity index 88% rename from labs/15-pitot/pitot-distribution/tsconfig.json rename to labs/15-pitot/pitot-distribution/sdk/typescript/tsconfig.json index 7c4d6a382..2e549458e 100644 --- a/labs/15-pitot/pitot-distribution/tsconfig.json +++ b/labs/15-pitot/pitot-distribution/sdk/typescript/tsconfig.json @@ -9,5 +9,5 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, - "include": ["types/ts/**/*"] + "include": ["src/**/*"] } diff --git a/labs/15-pitot/scripts/build_pitot.py b/labs/15-pitot/scripts/build_pitot.py index 3498af857..77cfafc32 100644 --- a/labs/15-pitot/scripts/build_pitot.py +++ b/labs/15-pitot/scripts/build_pitot.py @@ -57,10 +57,6 @@ def _iter_preview_files(repo: Path) -> list[tuple[str, Path]]: source = repo / PREVIEW / name if source.is_file(): entries.append((name, source)) - for name in ("package.json", "tsconfig.json"): - source = repo / LAB / "pitot-distribution" / name - if source.is_file(): - entries.append((name, source)) assets = repo / PREVIEW / "assets" if assets.is_dir(): for path in sorted(assets.rglob("*")): @@ -70,13 +66,15 @@ def _iter_preview_files(repo: Path) -> list[tuple[str, Path]]: return entries -def _iter_types_files(repo: Path) -> list[tuple[str, Path]]: - """Yield (projected_path, source_path) for generated language interfaces.""" +def _iter_sdk_files(repo: Path) -> list[tuple[str, Path]]: + """Yield (projected_path, source_path) for generated language SDKs.""" entries: list[tuple[str, Path]] = [] - types_dir = repo / LAB / "pitot-distribution" / "types" - if types_dir.is_dir(): - for path in sorted(types_dir.rglob("*")): + sdk_dir = repo / LAB / "pitot-distribution" / "sdk" + if sdk_dir.is_dir(): + for path in sorted(sdk_dir.rglob("*")): if path.is_file() and not path.is_symlink(): + if "__pycache__" in path.parts or path.suffix == ".pyc": + continue projected = path.relative_to(repo / LAB / "pitot-distribution").as_posix() entries.append((projected, path)) return entries @@ -86,7 +84,7 @@ def compute_manifest(repo: Path) -> dict[str, str]: """Return {projected_path: sha256} for the whole public surface.""" repo = repo.resolve() surface: dict[str, str] = {} - for projected, source in _iter_module_files(repo) + _iter_preview_files(repo) + _iter_types_files(repo): + for projected, source in _iter_module_files(repo) + _iter_preview_files(repo) + _iter_sdk_files(repo): if projected in surface: raise ValueError(f"projection collision on {projected}") digest = hashlib.sha256(source.read_bytes()).hexdigest() diff --git a/labs/15-pitot/scripts/generate_types.sh b/labs/15-pitot/scripts/generate_types.sh index eb6f7681a..5ad77ad7e 100755 --- a/labs/15-pitot/scripts/generate_types.sh +++ b/labs/15-pitot/scripts/generate_types.sh @@ -6,13 +6,19 @@ cd "$REPO_ROOT/labs/15-pitot/pitot" go run ./cmd/generate-schema > schema.json -TARGET_DIR="../pitot-distribution/types/ts" -mkdir -p "$TARGET_DIR" +# TypeScript +TS_DIR="../pitot-distribution/sdk/typescript/src" +mkdir -p "$TS_DIR" +npx -y quicktype -s schema schema.json -o "$TS_DIR/pitot.ts" --just-types +echo "export * from \"./pitot\";" > "$TS_DIR/index.ts" +echo "Generated TypeScript types in $TS_DIR" -npx -y quicktype -s schema schema.json -o "$TARGET_DIR/pitot.ts" --just-types +# Python +PY_DIR="../pitot-distribution/sdk/python/pitot" +mkdir -p "$PY_DIR" +npx -y quicktype -s schema schema.json -o "$PY_DIR/types.py" +# Create empty __init__.py if it doesn't exist +touch "$PY_DIR/__init__.py" +echo "Generated Python types in $PY_DIR" rm schema.json - -echo "export * from \"./pitot\";" > "$TARGET_DIR/index.ts" - -echo "Generated TypeScript types in $TARGET_DIR/pitot.ts" From b2bb8e22b741167683870debab7723cfdb3bba42 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Tue, 21 Jul 2026 14:33:41 +0100 Subject: [PATCH 5/5] fix(ci): synchronize UPSTREAM manifest after merge --- labs/15-pitot/pitot-distribution/UPSTREAM.json | 1 - 1 file changed, 1 deletion(-) diff --git a/labs/15-pitot/pitot-distribution/UPSTREAM.json b/labs/15-pitot/pitot-distribution/UPSTREAM.json index 047b9866f..220e63615 100644 --- a/labs/15-pitot/pitot-distribution/UPSTREAM.json +++ b/labs/15-pitot/pitot-distribution/UPSTREAM.json @@ -41,7 +41,6 @@ "sensor/decode_fuzz_test.go": "6f3412865c16e3314980a369c36f1f26ba36a991c8b4a8a9d3b776ad9c0de2b0", "sensor/sensor.go": "939e146eaa51906972f98cf4615747eb75811b0c54d467a938825750336abeae", "sensor/sensor_test.go": "ea9a58d45ad56d29214a40fb2cfa935e769f85334afcb1856a20fb938d486245", - "types/ts/pitot.ts": "9c243824cbb7edc54b1e125abfd828bf2ed77e7151a0bd5c5d4f63e81ca9b00c", "windtunnel/doc.go": "44e0bcde632da73e1f8b98beade3a34ca8e0d0ea79cdfb91d131de290b164fc4", "windtunnel/windtunnel_test.go": "1c8b6c5d56ff5c4e66403baeae0d98108255d153c1275572b8c68e333e091493" },