From dec071f5fd51d91c0d5521ba3059083491615cbe Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 23 Oct 2026 22:33:18 +0000 Subject: [PATCH 1/3] ci: clear the dependency audit (smol-toml, vite, brace-expansion, source-map-js) The Dependency audit step fails on new advisories, so typecheck, tests and e2e never run: - smol-toml <=1.8.0 (GHSA-r4xh-jqrq-34v2): ^1.7.1 -> ^1.9.1 (direct) - vite >=8.0.0 <=8.1.5 (GHSA-9jrq-w75r-8gcw, GHSA-rq7h-c2jc-7f22 and the bundled launch-editor advisories): ^8.0.16 -> ^8.1.6 (direct, dev) - brace-expansion >=4.0.0 <5.0.11 (GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p, GHSA-q2hr-2g5m-vwhr): override >=5.0.9 -> >=5.0.11 - source-map-js <1.2.2 (GHSA-68fv-2mgg-jv7q, via postcss): new override ^1.2.2 bun audit is clean; no advisory needed an ignore. Co-Authored-By: Claude Opus 5.5 (1M context) --- bun.lock | 89 +++++++++++++++++++++++----------------------------- package.json | 9 +++--- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/bun.lock b/bun.lock index 040b08d..4cf6d93 100644 --- a/bun.lock +++ b/bun.lock @@ -18,7 +18,7 @@ "react-dom": "^19.0.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", - "smol-toml": "^1.7.1", + "smol-toml": "^1.9.1", }, "devDependencies": { "@playwright/test": "^1.60.0", @@ -28,27 +28,22 @@ "@vitejs/plugin-react": "^6.0.2", "openapi-typescript": "^7.13.0", "typescript": "~6.0.3", - "vite": "^8.0.16", + "vite": "^8.1.6", }, }, }, "overrides": { - "brace-expansion": ">=5.0.9", + "brace-expansion": ">=5.0.11", "js-yaml": "^4.3.2", "nanoid": "^3.3.18", "postcss": ">=8.5.18", + "source-map-js": "^1.2.2", }, "packages": { "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], - - "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], - "@gascity/auth": ["@gascity/auth@0.1.6", "https://npm.pkg.github.com/download/@gascity/auth/0.1.6/d9c2a885000bbf2e76254c19afcbf36330b02666", { "dependencies": { "@gascity/shell": "*" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-pLO37KYoY7IapPiuWcCU5Mpltn7SOFM01bEEZ3vs2fpDNyAcr8Cte7SkNoHpbjSZyWoGC4SETNb0wNQZG2y1AQ=="], "@gascity/panel-sdk": ["@gascity/panel-sdk@0.0.3", "https://npm.pkg.github.com/download/@gascity/panel-sdk/0.0.3/33de85f1891efba77b12faf3ddb8112ba03626cd", { "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-NAxVUqbKETaMQKVAUhu7jLt4d+w5gXBCZzmD1lxr0s6N3DlR3jQxvyJyapQCA6PlW1qWrdgOlzcTf9LJ63A2qg=="], @@ -59,13 +54,11 @@ "@gascity/ui": ["@gascity/ui@0.7.2", "https://npm.pkg.github.com/download/@gascity/ui/0.7.2/f18c2ae2eb67f93fa2d0f22f8f364cb7d8530135", { "dependencies": { "@gascity/tokens": "^0.4.1" }, "peerDependencies": { "@gascity/shell": "*", "react": ">=18", "react-dom": ">=18" } }, "sha512-HHHnpxZ193AZvBnpP6wqDN+zBG2kfVGHe0qtjxdBMxc8UNZyprcTxpxQoCDvMsXXMDWHgQU0xS2WybPywumu9w=="], - "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="], - "@openpanel/sdk": ["@openpanel/sdk@1.3.1", "", {}, "sha512-mQ5xaBUGXnyRg3qYxwnbVaUYyW7w8u+munrWk/ClnqxEeR2j+FYPiA6noHbHMCFC2aZutYD5OoRzEvw3FBNFxw=="], "@openpanel/web": ["@openpanel/web@1.4.1", "", { "dependencies": { "@openpanel/sdk": "1.3.1", "@rrweb/types": "2.0.0-alpha.20", "rrweb": "2.0.0-alpha.20" } }, "sha512-DgUWeocZw+b57YiJjefZPg+6sceR7TdOF8x7Ut75lu3CwcwqGi5jZNPXjikNemGL37aNWXCB4buhcoNan2j7RA=="], - "@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="], + "@oxc-project/types": ["@oxc-project/types@0.155.0", "", {}, "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q=="], "@playwright/test": ["@playwright/test@1.61.0", "", { "dependencies": { "playwright": "1.61.0" }, "bin": { "playwright": "cli.js" } }, "sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA=="], @@ -75,35 +68,35 @@ "@redocly/openapi-core": ["@redocly/openapi-core@1.34.17", "", { "dependencies": { "@redocly/ajv": "8.11.2", "@redocly/config": "0.22.0", "colorette": "1.4.0", "https-proxy-agent": "7.0.6", "js-levenshtein": "1.1.6", "js-yaml": "4.2.0", "minimatch": "5.1.9", "pluralize": "8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-wsV2keCt6B806XpSdezbWZ9aFJYf14YVh+XQf0ESt7M90yqVuxH9//PxvtC70sgj9OCkRM3nRaLfu4MsGQZRig=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="], + "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.15", "", { "os": "android", "cpu": "arm" }, "sha512-aLcgK9gIJ3YRvrrtfSBbRa27F5WrpiTaPulnFpsHPxpUHgFlSgnU4854MHwoRGEBhfRJDWCS2WQ7lIjskE9Olw=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.2.15", "", { "os": "android", "cpu": "arm64" }, "sha512-xD7DdZCrbThKuxNg7ooQmvp07ys4J/pnSEMTG3/OdOWl6daC67m/Q4fL78sRHMxTH/N4fGUbYhcPM0MF5WFHkg=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.2.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-m2WuQ6r0B4OTYxyPpNLtXaMRbRDUbG8YQV2Q3KTdSSK8Th6KHCnC8jVCJt8bvYeSB5WivkkNbc85mKadaSdOxA=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.2.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-5RtnsHM36nodM9WbMy+zip0LGS/G+y5wro5RHrpOlr4ac24+1zSi9kiV2MP1myZeZ/ifEHfulQcWjnAXzc2C2A=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.2.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-g3l09pyFqhu+135JKkbkwiKGQmwU5hUMUPJ4VdBADvuqR8d33L6H578rh8m7rtxbdp+VmX/+nzI7nvhSZlzpUQ=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.2.15", "", { "os": "linux", "cpu": "arm" }, "sha512-nIe3VugI97nGQtspPxDqA4sXMUpeiocKIP/CqpY/u/vgbT+alX+cVxu1C/oVBPKg4feNTmYovE91v4DgXvXNwQ=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.2.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-iTBjXx+B5QSvgI89lJfmqh4wTh8NabR1YZIIN3hNx4zSG0EVfVqkOvLulNpG4251pbd6/IAmZ/LefVk1Y4rPhw=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.2.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-uN26XOnVR7rqQupG3V9oAG2EGGatWjcJHOeDOHUFCh4TBWmP2tHTFoVN13aYbwfm0aHqJAEmcwhAu6sooIjfqQ=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.2.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9nWkcdd+H+XhaA3fQIwpHnKIrfx/PQ6rO0pivr78Og07tXlUlctBBJoEYLPfhTmCR/QxEVdn6uPjHV5SymPhdw=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.2.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-Ggh0ciFjLwIusLSLz7LZYEaGdxOAUJXI+Qu5JCbhPJJHEt8vP+IjRATTJheiJsdzzTYgUiHUiw3X1Ea3ZpMBdA=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.2.15", "", { "os": "linux", "cpu": "x64" }, "sha512-FHg60rS6DBePAYZ9X9F67x+JObFdmgn6tupA1C/a3B+CufFmTD4mX+e8M75tCiAO3foR/6xwVBI0KQiI7sn/rw=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.3", "", { "os": "none", "cpu": "arm64" }, "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.2.15", "", { "os": "linux", "cpu": "x64" }, "sha512-/cvV/7v5VGz/moiz0XKRj2dE8DzsKyhIu1KpOvJGPjRywg3yscON889UGvAe8zfiJLHup7VLjBduoGWi3HusWQ=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.3", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.2.15", "", { "os": "none", "cpu": "arm64" }, "sha512-MyzC5Hrv/sm+Z7TSGKP/j8001RO2xQm1zOpCNVL4j0cY/VhBZviludA2YFQ7i4lH6ZAXxQm0lcMdBzM+99mE7w=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.2.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-+PgYiS+AH5omy6L45ZLxybasauJaj3M8buoT83D+nzPmCayXlxUV9hydhciCKVyIg0B/v0BtXPDE1uGqNwCPpg=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.2.15", "", { "os": "win32", "cpu": "x64" }, "sha512-HFZd2QNQYn1JbIi+Hpd7H+r/lkWxcfIu28nirZ6eCn2LafuvmExMlb/P96Sfx5tf7pPArw39IdhmCF8vmV+NhQ=="], "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], @@ -111,8 +104,6 @@ "@rrweb/utils": ["@rrweb/utils@2.0.1", "", {}, "sha512-t+b+4rB23/e4EpoGMdPUr6cKtDsFuOb3LxA9IHE8e3A/Xo0nMUfH0bkwqdpUNa/VGDOdiGeG3bhHjuO9zWX54g=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], - "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/css-font-loading-module": ["@types/css-font-loading-module@0.0.7", "", {}, "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q=="], @@ -155,7 +146,7 @@ "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="], - "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], @@ -231,29 +222,29 @@ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], @@ -363,7 +354,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], "playwright": ["playwright@1.61.0", "", { "dependencies": { "playwright-core": "1.61.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ=="], @@ -393,7 +384,7 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - "rolldown": ["rolldown@1.0.3", "", { "dependencies": { "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.3", "@rolldown/binding-darwin-arm64": "1.0.3", "@rolldown/binding-darwin-x64": "1.0.3", "@rolldown/binding-freebsd-x64": "1.0.3", "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", "@rolldown/binding-linux-arm64-gnu": "1.0.3", "@rolldown/binding-linux-arm64-musl": "1.0.3", "@rolldown/binding-linux-ppc64-gnu": "1.0.3", "@rolldown/binding-linux-s390x-gnu": "1.0.3", "@rolldown/binding-linux-x64-gnu": "1.0.3", "@rolldown/binding-linux-x64-musl": "1.0.3", "@rolldown/binding-openharmony-arm64": "1.0.3", "@rolldown/binding-wasm32-wasi": "1.0.3", "@rolldown/binding-win32-arm64-msvc": "1.0.3", "@rolldown/binding-win32-x64-msvc": "1.0.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g=="], + "rolldown": ["rolldown@1.2.15", "", { "dependencies": { "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.15", "@rolldown/binding-android-arm64": "1.2.15", "@rolldown/binding-darwin-arm64": "1.2.15", "@rolldown/binding-darwin-x64": "1.2.15", "@rolldown/binding-freebsd-x64": "1.2.15", "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", "@rolldown/binding-linux-arm64-gnu": "1.2.15", "@rolldown/binding-linux-arm64-musl": "1.2.15", "@rolldown/binding-linux-ppc64-gnu": "1.2.15", "@rolldown/binding-linux-s390x-gnu": "1.2.15", "@rolldown/binding-linux-x64-gnu": "1.2.15", "@rolldown/binding-linux-x64-musl": "1.2.15", "@rolldown/binding-openharmony-arm64": "1.2.15", "@rolldown/binding-win32-arm64-msvc": "1.2.15", "@rolldown/binding-win32-x64-msvc": "1.2.15" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g=="], "rrdom": ["rrdom@2.0.1", "", { "dependencies": { "rrweb-snapshot": "^2.0.1" } }, "sha512-z8GdQWFmVfa8GpcGkAqfBXseb89z0ciTPSBkID0qr0i6KHCye1MdQ4VoXFf0Ejp3ELe/c7AObu4zsbSbMT+hiw=="], @@ -403,9 +394,9 @@ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "smol-toml": ["smol-toml@1.8.0", "", {}, "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ=="], + "smol-toml": ["smol-toml@1.9.1", "", {}, "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="], "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], @@ -423,8 +414,6 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], @@ -449,7 +438,7 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@8.0.16", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw=="], + "vite": ["vite@8.3.5", "", { "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.30", "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ=="], "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], @@ -460,5 +449,7 @@ "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + + "tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], } } diff --git a/package.json b/package.json index 5fb458a..b09e039 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "react-dom": "^19.0.0", "react-markdown": "^10.1.0", "remark-gfm": "^4.0.1", - "smol-toml": "^1.7.1", + "smol-toml": "^1.9.1", "@gascity/tokens": "^0.4.1", "@gascity/ui": "^0.7.2", "@gascity/shell": "^0.9.3", @@ -46,12 +46,13 @@ "@vitejs/plugin-react": "^6.0.2", "openapi-typescript": "^7.13.0", "typescript": "~6.0.3", - "vite": "^8.0.16" + "vite": "^8.1.6" }, "overrides": { - "brace-expansion": ">=5.0.9", + "brace-expansion": ">=5.0.11", "js-yaml": "^4.3.2", "nanoid": "^3.3.18", - "postcss": ">=8.5.18" + "postcss": ">=8.5.18", + "source-map-js": "^1.2.2" } } From 811c94ee742d98e4add303cc570096c5f3fba20d Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 23 Oct 2026 22:33:18 +0000 Subject: [PATCH 2/3] registry: follow the upstream contributing -> contributing-to-gascity rename gascity-packs renamed the `contributing` pack to `contributing-to-gascity` (release 0.5.0, "First release under the new name"). The expected_packs trust list still named the old pack, so `bun run generate` refuses the source ("unexpected pack ... is not declared in expected_packs"). That breaks the browser e2e web server, which regenerates the aggregate before starting, and the hourly aggregate refresh. Rename the entry in sources.toml and regenerate the committed aggregate outputs. The pack key changes from gascity-packs--contributing to gascity-packs--contributing-to-gascity, following upstream. Co-Authored-By: Claude Opus 5.5 (1M context) --- public/catalog.json | 158 ++++++++++++------ public/og/bmad.svg | 4 +- public/og/compound-engineering.svg | 4 +- ...buting.svg => contributing-to-gascity.svg} | 8 +- public/og/gstack.svg | 4 +- public/og/registry.svg | 2 +- public/og/slack-channel.svg | 4 +- public/og/slack-full.svg | 4 +- public/og/slack-mini.svg | 4 +- public/og/superpowers.svg | 4 +- public/registry.toml | 84 +++++++--- sources.toml | 2 +- 12 files changed, 186 insertions(+), 96 deletions(-) rename public/og/{contributing.svg => contributing-to-gascity.svg} (92%) diff --git a/public/catalog.json b/public/catalog.json index f37813c..e9b06ce 100644 --- a/public/catalog.json +++ b/public/catalog.json @@ -26,13 +26,13 @@ "description": "BMAD Method delivery process (PRD, architecture, epics and stories, readiness, adversarial review) as a Gas City build factory.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/bmad", "source_kind": "git", - "search_text": "bmad gascity-packs maintained gas city bmad method delivery process (prd, architecture, epics and stories, readiness, adversarial review) as a gas city build factory. https://github.com/gastownhall/gascity-packs/tree/main/bmad git # bmad pack [bmad method](https://github.com/bmad-code-org/bmad-method) is a document-first delivery process: write a prd, design the architecture, decompose it into epics and stories, gate on implementation readiness, implement story by story, then run adversarial code review. this pack vendors the bmad method skills and runs that process as a durable gas city build factory: every stage is a bead in a graph that gas city can retry, observe, and resume, and the result is a reviewed implementation in your repository with a full artifact trail. ## when to choose bmad - you want disciplined, document-first delivery: an explicit prd and architecture document exist before any code, and an implementation-readiness gate blocks coding until the stories are actually ready. - you want per-story rigor: each story gets its own implement, self-check, acceptance-audit, and fix loop, followed by a four-lane adversarial code review (blind-hunter, edge-case, acceptance-audit, gap-analysis). - prefer `build-basic` (in the `gascity` pack) when you want the default starter factory with fewer stages and a single-lane review. - prefer `compound-engineering` when review breadth matters most (20+ specialist reviewer lanes), or `superpowers` when you want hard spec-approval gates and strict per-task tdd. ## quick start prerequisites: gas city installed and a city running, plus a git repository registered as a rig. if you have not done that yet: ```sh brew install gascity gc init ~/my-city cd ~/my-city gc start ``` ```sh mkdir proj && cd proj && git init gc rig add . ``` 1. **import the pack.** from the city directory, add bmad at city scope. this writes the import, fetches the latest release, and pins it in `packs.lock` — no clone needed. the pack imports the gas city base pack internally as `gc`, so `build-base` and the `gc.*` formula surface are available transitively: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//bmad ``` 2. **import the rig roles in `city.toml`.** the rig additionally needs the `gascity/roles` import so the `gc.*` role agents (operator, publisher, and friends) can run work inside the target repository; run `gc import install` after editing: ```toml [[rigs]] name = \"proj\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/bmad`) instead. 3. **launch your first build.** `bmad-build` is targeted (`target_required = true`), so create a bead describing the goal first, then sling the formula at it from the target rig context: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` `artifact_root` is the only required variable; everything else has a default (see customization below). 4. **what happens next.** the workflow walks these stages in order: prepare, prd requirements, architecture plan, architecture handoff review, epics/stories decomposition, implementation readiness, the story implementation drain, adversarial code review, finalize, publish. stage artifacts (prd, architecture document, decomposition, readiness report, review reports, final summary) land under `artifact_root` in your rig. 5. **watch progress.** the workflow root bead carries the run state and the `gc.build.*` handoff metadata; list beads with `gc bd --rig ` and convoys with `gc convoy --rig `. the default `interaction_mode` is `interactive`, so the run halts for your input at bmad checkpoints — each halt records a resume note, and `gc session attach ` drops you into the waiting session. pass `--var interaction_mode=autonomous` if you would rather it run unattended. ## stage map `bmad-build` extends `build-base` and keeps the inherited anchor order `prepare -> requirements -> plan -> plan-review -> decompose -> implement/implement-same-session -> review -> finalize -> publish`. the pack overrides `requirements` (prd), `plan` (architecture), `plan-review` (architecture handoff review), `decompose` (epics and stories), `implement`/`implement-same-session` (story-development drains), and `review` (adversarial code-review loop); `prepare`, `finalize`, and `publish` stay inherited. `implementation-readiness` is the only pack-added step: it needs `decompose`, both implementation drains need it, and its gate is the readiness review run by `bmad.readiness-reviewer`, which records the readiness report path and outcome on the workflow root bead before implementation begins. no base anchor is renamed, skipped, or reordered. the native stage formulas extend the matching base methodology contracts: `bmad-planning` (`planning-base`), `bmad-decomposition` (`decomposition-base`), `bmad-implementation` (`implement`), `bmad-review` (`code-review-base`), and `bmad-fix-loop` (`fix-loop-base`). `bmad-build` pins them as its selector defaults (`planning_formula`, `decomposition_formula`, `implementation_formula`, `implementation_item_formula` `bmad-story-development-item`, `code_review_formula`, `review_fix_formula`) with `implementation_target` defaulting to `bmad.story-implementer`. supported modes and drain policies, as declared in `[metadata.gc.methodology]`: | axis | values | default | meaning | | ---- | ------ | ------- | ------- | | `interaction_mode` | `interactive`, `autonomous`, `headless` | `interactive` | bmad's menu/checkpoint axis: interactive runs preserve halts and user choices; headless automation expects all required inputs up front. | | `review_mode` | `report`, `agent`, `interactive` | `agent` | `report` synthesizes findings without applying fixes; `agent` and `interactive` feed the fix loop until the approval check passes. | | `drain_policy` | `separate`, `same-session` | `separate` | `separate` drains `bmad-story-development` item formulas in parallel worktrees with exclusive member access; `same-session` drains `bmad-story-development-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`. | ```mermaid flowchart td classdef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef bmad fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch bmad-build\"]:::terminal --> prepare[\"prepare
inherited build-base\"]:::inherited prepare --> prd[\"requirements
bmad prd override\"]:::bmad prd --> architecture[\"plan
bmad architecture override\"]:::bmad architecture --> archreview[\"plan-review
bmad architecture handoff override\"]:::bmad archreview --> decompose[\"decompose
bmad epics and stories override\"]:::bmad decompose --> readiness[\"implementation-readiness
bmad readiness gate override\"]:::bmad readiness --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra subgraph storyformula[\"bmad-story-development item formula: one formula instance per story bead\"] direction tb separateprep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> storyloopstart[\"run bmad story development
override control step\"]:::bmad sharedstorystart[\"run bmad shared story development
override implement-item\"]:::bmad storyloopstart --> storysetup[\"setup-bmad-story-development\"]:::bmad sharedstorystart --> storysetup storysetup --> implementstory[\"implement-story\"]:::bmad implementstory --> selfcheck[\"story-self-check\"]:::bmad selfcheck --> acceptanceaudit[\"acceptance-audit\"]:::bmad acceptanceaudit --> applystory[\"apply-story-findings\"]:::bmad applystory --> storygate{\"story clean?
implementation-review check\"}:::infra storygate -->|no: fix story and re-check| storysetup storygate -->|yes| storydone[\"story accepted\"]:::bmad storydone --> closestory[\"close source anchor
inherited do-work
separate drain only\"]:::inherited end drainseparate --> separateprep drainshared --> sharedstorystart closestory --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra storydone --> drainfanin drainfanin --> reviewsetup[\"review
bmad code-review override\"]:::bmad subgraph codereview[\"post-implementation review override: bmad review loop\"] direction tb reviewsetup --> gathercontext[\"gather-bmad-review-context\"]:::bmad gathercontext --> reviewfanout[\"fan out bmad review lanes\"]:::infra subgraph reviewlanes[\"parallel reviewer lanes: sibling beads with no needs between them\"] direction lr blindhunter[\"blind-hunter-review\"]:::bmad edgecase[\"edge-case-review\"]:::bmad acceptancereview[\"acceptance-auditor-review\"]:::bmad gapreview[\"gap-analysis-review\"]:::bmad end reviewfanout --> blindhunter reviewfanout --> edgecase reviewfanout --> acceptancereview reviewfanout --> gapreview blindhunter --> reviewfanin[\"fan in
wait for all review artifacts\"]:::infra edgecase --> reviewfanin acceptancereview --> reviewfanin gapreview --> reviewfanin reviewfanin --> reviewsynth[\"synthesize-bmad-review\"]:::bmad reviewsynth --> applyreview[\"apply-bmad-review-findings\"]:::bmad applyreview --> reviewgate{\"implementation approved?
graph check\"}:::infra reviewgate -->|no: fix and re-review| reviewfanout reviewgate -->|yes| reviewdone[\"finalize bmad code review\"]:::bmad end reviewdone --> finalize[\"finalize
inherited build-base\"]:::inherited finalize --> publish[\"publish
inherited build-base\"]:::inherited publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are inherited gas city behavior, green nodes are bmad-specific overrides, and amber nodes are gas city graph, convoy, or drain infrastructure. the story-development item formula loops per implementation bead until the story self-check and acceptance audit are clean. after the convoy drains, the bmad code-review loop fans out adversarial review lanes, synthesizes findings, applies required fixes, and repeats until the implementation-review check passes. those reviewer lanes are real sibling beads — the fan-in barrier in the diagram documents the synthesizer's `needs` list, not a serial execution step between reviewers. ## customization launch variables you are most likely to set (all passed as `--var k=v`): | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | required | directory in the rig where all stage artifacts are written. | | `interaction_mode` | `interactive` | human participation: `interactive` preserves bmad halts and menus, `autonomous` makes reasonable decisions while recording evidence, `headless` is for automation with no blocking prompts. | | `review_mode` | `agent` | review authority: `report` writes findings only; `agent`/`interactive` apply fixes through the fix loop until approval. | | `drain_policy` | `separate` | `separate` runs stories in parallel worktrees; `same-session` runs them sequentially in one shared session. | | `implementation_target` | `bmad.story-implementer` | role agent that implements stories and applies review fixes. | | `push` | `false` | set `true` to allow the final publish stage to push after all checks pass. | | `open_pr` | `false` | set `true` to allow the final publish stage to open a pr after all checks pass. | | `max_iterations` | `10` | maximum implementation/review fix attempts before the loop gives up. | | `planning_formula`, `decomposition_formula`, `implementation_formula`, `implementation_item_formula`, `code_review_formula`, `review_fix_formula` | `bmad-planning`, `bmad-decomposition`, `bmad-implementation`, `bmad-story-development-item`, `bmad-review`, `bmad-fix-loop` | selector surface: swap any stage methodology for another formula that satisfies the matching base contract. | to change what a stage tells its agent without touching the graph, shadow the stage prompt asset: put a markdown file at the same relative path in a higher-priority city or local pack layer. for example, to replace the prd stage prompt, create `assets/workflows/bmad-build/requirements.md` in your city assets — gas city resolves `description_file` assets through the normal import/layer search path, so your file replaces this pack's prompt text. every stage body under `bmad/assets/workflows/` can be shadowed the same way. for advanced step overrides (replacing a step with another formula, expansion, or fanout), see \"stable workflow override interface\" in the [gascity pack readme](../gascity/readme.md). ## examples interactive first feature build — the default posture. halts at bmad checkpoints for your decisions, runs stories in parallel worktrees, and stops before any push or pr: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` autonomous run that ships — no blocking prompts, review findings fixed by the agent loop, and publish allowed to push the branch and open a pr once every check passes: ```sh gc bd create \"migrate the config loader from json to toml\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/toml-config/build \\ --var interaction_mode=autonomous \\ --var review_mode=agent \\ --var push=true \\ --var open_pr=true ``` same-session drain for a small task — all stories run sequentially in one shared worker session and worktree instead of fanning out, which suits small changes where parallel worktrees are overhead: ```sh gc bd create \"fix off-by-one in pagination cursor\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/pagination-fix/build \\ --var interaction_mode=autonomous \\ --var drain_policy=same-session ``` ## what's vendored - formula: `bmad-build` - methodology formulas: `bmad-planning`, `bmad-decomposition`, `bmad-implementation`, `bmad-review`, and `bmad-fix-loop` - expansion formulas: `bmad-code-review-flow` - implementation item formulas: `bmad-story-development`, `bmad-story-development-item` - pack agents: `bmad.prd-writer`, `bmad.architect`, `bmad.epic-story-decomposer`, `bmad.readiness-reviewer`, `bmad.story-implementer`, `bmad.story-self-checker`, `bmad.acceptance-auditor`, `bmad.blind-hunter-reviewer`, `bmad.edge-case-reviewer`, and `bmad.bmad-review-synthesizer` - vendored skills: `bmad-prd`, `bmad-create-architecture`, `bmad-check-implementation-readiness`, `bmad-create-epics-and-stories`, `bmad-quick-dev`, `bmad-dev-story`, `bmad-code-review`, `bmad-brainstorming`, and `bmad-spec` - provenance: `vendor/bmad-method/upstream.toml` records the upstream repository, pinned commit, mit license, and vendored paths bmad quick-dev and code-review describe sub-agent/task handoffs and parallel review layers. this pack converts those shapes into gas city item formulas and expansion formulas with explicit implementation, self-check, acceptance-audit, adversarial review, synthesis, and fix lanes. the vendored skill files are source material only; the workflow must not invoke provider-native subagents, slash commands, task tools, or the upstream plugin runtime. bmad structured steps are preserved as graph structure wherever possible. the pack keeps bmad's step-file discipline as prompt guidance, but turns repeated implementation and review handoffs into formulas, check loops, and fanout lanes so the work is durable, resumable, and visible in gas city. when bmad text says to launch a task or review subagent, read that as a request for a gas city lane or expansion child. ## compatibility ledger the pack-local compatibility ledger lives at [`bmad/requirements.md`](./requirements.md) and records the build-base contract proofs — the inherited `gc` import, preserved anchor order, mode and drain declarations, selector defaults, and the evidence commands that reproduce each claim. 0.1.0 first registry release of the bmad build factory.", + "search_text": "bmad gascity-packs maintained gas city bmad method delivery process (prd, architecture, epics and stories, readiness, adversarial review) as a gas city build factory. https://github.com/gastownhall/gascity-packs/tree/main/bmad git # bmad pack [bmad method](https://github.com/bmad-code-org/bmad-method) is a document-first delivery process: write a prd, design the architecture, decompose it into epics and stories, gate on implementation readiness, implement story by story, then run adversarial code review. this pack vendors the bmad method skills and runs that process as a durable gas city build factory: every stage is a bead in a graph that gas city can retry, observe, and resume, and the result is a reviewed implementation in your repository with a full artifact trail. ## when to choose bmad - you want disciplined, document-first delivery: an explicit prd and architecture document exist before any code, and an implementation-readiness gate blocks coding until the stories are actually ready. - you want per-story rigor: each story gets its own implement, self-check, acceptance-audit, and fix loop, followed by a four-lane adversarial code review (blind-hunter, edge-case, acceptance-audit, gap-analysis). - prefer `build-basic` (in the `gascity` pack) when you want the default starter factory with fewer stages and a single-lane review. - prefer `compound-engineering` when review breadth matters most (20+ specialist reviewer lanes), or `superpowers` when you want hard spec-approval gates and strict per-task tdd. ## quick start prerequisites: gas city installed and a city running, plus a git repository registered as a rig. if you have not done that yet: ```sh brew install gascity gc init ~/my-city cd ~/my-city gc start ``` ```sh mkdir proj && cd proj && git init gc rig add . ``` 1. **import the pack.** from the city directory, add bmad at city scope. this writes the import, fetches the latest release, and pins it in `packs.lock` — no clone needed. the pack imports the gas city base pack internally as `gc`, so `build-base` and the `gc.*` formula surface are available transitively: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//bmad ``` 2. **import the rig roles in `city.toml`.** the rig additionally needs the `gascity/roles` import so the `gc.*` role agents (operator, publisher, and friends) can run work inside the target repository; run `gc import install` after editing: ```toml [[rigs]] name = \"proj\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/bmad`) instead. 3. **launch your first build.** `bmad-build` is targeted (`target_required = true`), so create a bead describing the goal first, then sling the formula at it from the target rig context: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` `artifact_root` is the only required variable; everything else has a default (see customization below). 4. **what happens next.** the workflow walks these stages in order: prepare, prd requirements, architecture plan, architecture handoff review, epics/stories decomposition, implementation readiness, the story implementation drain, adversarial code review, finalize, publish. stage artifacts (prd, architecture document, decomposition, readiness report, review reports, final summary) land under `artifact_root` in your rig. 5. **watch progress.** the workflow root bead carries the run state and the `gc.build.*` handoff metadata; list beads with `gc bd --rig ` and convoys with `gc convoy --rig `. the default `interaction_mode` is `interactive`, so the run halts for your input at bmad checkpoints — each halt records a resume note, and `gc session attach ` drops you into the waiting session. pass `--var interaction_mode=autonomous` if you would rather it run unattended. ## stage map `bmad-build` extends `build-base` and keeps the inherited anchor order `prepare -> requirements -> plan -> plan-review -> decompose -> implement/implement-same-session -> review -> finalize -> publish`. the pack overrides `requirements` (prd), `plan` (architecture), `plan-review` (architecture handoff review), `decompose` (epics and stories), `implement`/`implement-same-session` (story-development drains), and `review` (adversarial code-review loop); `prepare`, `finalize`, and `publish` stay inherited. `implementation-readiness` is the only pack-added step: it needs `decompose`, both implementation drains need it, and its gate is the readiness review run by `bmad.readiness-reviewer`, which records the readiness report path and outcome on the workflow root bead before implementation begins. no base anchor is renamed, skipped, or reordered. the native stage formulas extend the matching base methodology contracts: `bmad-planning` (`planning-base`), `bmad-decomposition` (`decomposition-base`), `bmad-implementation` (`implement`), `bmad-review` (`code-review-base`), and `bmad-fix-loop` (`fix-loop-base`). `bmad-build` pins them as its selector defaults (`planning_formula`, `decomposition_formula`, `implementation_formula`, `implementation_item_formula` `bmad-story-development-item`, `code_review_formula`, `review_fix_formula`) with `implementation_target` defaulting to `bmad.story-implementer`. supported modes and drain policies, as declared in `[metadata.gc.methodology]`: | axis | values | default | meaning | | ---- | ------ | ------- | ------- | | `interaction_mode` | `interactive`, `autonomous`, `headless` | `interactive` | bmad's menu/checkpoint axis: interactive runs preserve halts and user choices; headless automation expects all required inputs up front. | | `review_mode` | `report`, `agent`, `interactive` | `agent` | `report` synthesizes findings without applying fixes; `agent` and `interactive` feed the fix loop until the approval check passes. | | `drain_policy` | `separate`, `same-session` | `separate` | `separate` drains `bmad-story-development` item formulas in parallel worktrees with exclusive member access; `same-session` drains `bmad-story-development-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`. | ```mermaid flowchart td classdef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef bmad fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch bmad-build\"]:::terminal --> prepare[\"prepare
inherited build-base\"]:::inherited prepare --> prd[\"requirements
bmad prd override\"]:::bmad prd --> architecture[\"plan
bmad architecture override\"]:::bmad architecture --> archreview[\"plan-review
bmad architecture handoff override\"]:::bmad archreview --> decompose[\"decompose
bmad epics and stories override\"]:::bmad decompose --> readiness[\"implementation-readiness
bmad readiness gate override\"]:::bmad readiness --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra subgraph storyformula[\"bmad-story-development item formula: one formula instance per story bead\"] direction tb separateprep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> storyloopstart[\"run bmad story development
override control step\"]:::bmad sharedstorystart[\"run bmad shared story development
override implement-item\"]:::bmad storyloopstart --> storysetup[\"setup-bmad-story-development\"]:::bmad sharedstorystart --> storysetup storysetup --> implementstory[\"implement-story\"]:::bmad implementstory --> selfcheck[\"story-self-check\"]:::bmad selfcheck --> acceptanceaudit[\"acceptance-audit\"]:::bmad acceptanceaudit --> applystory[\"apply-story-findings\"]:::bmad applystory --> storygate{\"story clean?
implementation-review check\"}:::infra storygate -->|no: fix story and re-check| storysetup storygate -->|yes| storydone[\"story accepted\"]:::bmad storydone --> closestory[\"close source anchor
inherited do-work
separate drain only\"]:::inherited end drainseparate --> separateprep drainshared --> sharedstorystart closestory --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra storydone --> drainfanin drainfanin --> reviewsetup[\"review
bmad code-review override\"]:::bmad subgraph codereview[\"post-implementation review override: bmad review loop\"] direction tb reviewsetup --> gathercontext[\"gather-bmad-review-context\"]:::bmad gathercontext --> reviewfanout[\"fan out bmad review lanes\"]:::infra subgraph reviewlanes[\"parallel reviewer lanes: sibling beads with no needs between them\"] direction lr blindhunter[\"blind-hunter-review\"]:::bmad edgecase[\"edge-case-review\"]:::bmad acceptancereview[\"acceptance-auditor-review\"]:::bmad gapreview[\"gap-analysis-review\"]:::bmad end reviewfanout --> blindhunter reviewfanout --> edgecase reviewfanout --> acceptancereview reviewfanout --> gapreview blindhunter --> reviewfanin[\"fan in
wait for all review artifacts\"]:::infra edgecase --> reviewfanin acceptancereview --> reviewfanin gapreview --> reviewfanin reviewfanin --> reviewsynth[\"synthesize-bmad-review\"]:::bmad reviewsynth --> applyreview[\"apply-bmad-review-findings\"]:::bmad applyreview --> reviewgate{\"implementation approved?
graph check\"}:::infra reviewgate -->|no: fix and re-review| reviewfanout reviewgate -->|yes| reviewdone[\"finalize bmad code review\"]:::bmad end reviewdone --> finalize[\"finalize
inherited build-base\"]:::inherited finalize --> publish[\"publish
inherited build-base\"]:::inherited publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are inherited gas city behavior, green nodes are bmad-specific overrides, and amber nodes are gas city graph, convoy, or drain infrastructure. the story-development item formula loops per implementation bead until the story self-check and acceptance audit are clean. after the convoy drains, the bmad code-review loop fans out adversarial review lanes, synthesizes findings, applies required fixes, and repeats until the implementation-review check passes. those reviewer lanes are real sibling beads — the fan-in barrier in the diagram documents the synthesizer's `needs` list, not a serial execution step between reviewers. ## customization launch variables you are most likely to set (all passed as `--var k=v`): | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | required | directory in the rig where all stage artifacts are written. | | `interaction_mode` | `interactive` | human participation: `interactive` preserves bmad halts and menus, `autonomous` makes reasonable decisions while recording evidence, `headless` is for automation with no blocking prompts. | | `review_mode` | `agent` | review authority: `report` writes findings only; `agent`/`interactive` apply fixes through the fix loop until approval. | | `drain_policy` | `separate` | `separate` runs stories in parallel worktrees; `same-session` runs them sequentially in one shared session. | | `implementation_target` | `bmad.story-implementer` | role agent that implements stories and applies review fixes. | | `push` | `false` | set `true` to allow the final publish stage to push after all checks pass. | | `open_pr` | `false` | set `true` to allow the final publish stage to open a pr after all checks pass. | | `max_iterations` | `10` | maximum implementation/review fix attempts before the loop gives up. | | `planning_formula`, `decomposition_formula`, `implementation_formula`, `implementation_item_formula`, `code_review_formula`, `review_fix_formula` | `bmad-planning`, `bmad-decomposition`, `bmad-implementation`, `bmad-story-development-item`, `bmad-review`, `bmad-fix-loop` | selector surface: swap any stage methodology for another formula that satisfies the matching base contract. | to change what a stage tells its agent without touching the graph, shadow the stage prompt asset: put a markdown file at the same relative path in a higher-priority city or local pack layer. for example, to replace the prd stage prompt, create `assets/workflows/bmad-build/requirements.md` in your city assets — gas city resolves `description_file` assets through the normal import/layer search path, so your file replaces this pack's prompt text. every stage body under `bmad/assets/workflows/` can be shadowed the same way. for advanced step overrides (replacing a step with another formula, expansion, or fanout), see \"stable workflow override interface\" in the [gascity pack readme](../gascity/readme.md). ## examples interactive first feature build — the default posture. halts at bmad checkpoints for your decisions, runs stories in parallel worktrees, and stops before any push or pr: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` autonomous run that ships — no blocking prompts, review findings fixed by the agent loop, and publish allowed to push the branch and open a pr once every check passes: ```sh gc bd create \"migrate the config loader from json to toml\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/toml-config/build \\ --var interaction_mode=autonomous \\ --var review_mode=agent \\ --var push=true \\ --var open_pr=true ``` same-session drain for a small task — all stories run sequentially in one shared worker session and worktree instead of fanning out, which suits small changes where parallel worktrees are overhead: ```sh gc bd create \"fix off-by-one in pagination cursor\" gc sling gc.run-operator --on bmad-build \\ --var artifact_root=plans/pagination-fix/build \\ --var interaction_mode=autonomous \\ --var drain_policy=same-session ``` ## what's vendored - formula: `bmad-build` - methodology formulas: `bmad-planning`, `bmad-decomposition`, `bmad-implementation`, `bmad-review`, and `bmad-fix-loop` - expansion formulas: `bmad-code-review-flow` - implementation item formulas: `bmad-story-development`, `bmad-story-development-item` - pack agents: `bmad.prd-writer`, `bmad.architect`, `bmad.epic-story-decomposer`, `bmad.readiness-reviewer`, `bmad.story-implementer`, `bmad.story-self-checker`, `bmad.acceptance-auditor`, `bmad.blind-hunter-reviewer`, `bmad.edge-case-reviewer`, and `bmad.bmad-review-synthesizer` - vendored skills: `bmad-prd`, `bmad-create-architecture`, `bmad-check-implementation-readiness`, `bmad-create-epics-and-stories`, `bmad-quick-dev`, `bmad-dev-story`, `bmad-code-review`, `bmad-brainstorming`, and `bmad-spec` - provenance: `vendor/bmad-method/upstream.toml` records the upstream repository, pinned commit, mit license, and vendored paths bmad quick-dev and code-review describe sub-agent/task handoffs and parallel review layers. this pack converts those shapes into gas city item formulas and expansion formulas with explicit implementation, self-check, acceptance-audit, adversarial review, synthesis, and fix lanes. the vendored skill files are source material only; the workflow must not invoke provider-native subagents, slash commands, task tools, or the upstream plugin runtime. bmad structured steps are preserved as graph structure wherever possible. the pack keeps bmad's step-file discipline as prompt guidance, but turns repeated implementation and review handoffs into formulas, check loops, and fanout lanes so the work is durable, resumable, and visible in gas city. when bmad text says to launch a task or review subagent, read that as a request for a gas city lane or expansion child. ## compatibility ledger the pack-local compatibility ledger lives at [`bmad/requirements.md`](./requirements.md) and records the build-base contract proofs — the inherited `gc` import, preserved anchor order, mode and drain declarations, selector defaults, and the evidence commands that reproduce each claim. 0.1.0 first registry release of the bmad build factory. 0.1.1 implementer commits in the source-anchor worktree (only real changes); code review records a validated review report on the workflow root; apply-findings keeps artifacts schema-valid; picks up newer gascity pack commits (do-work now needs an origin remote).", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/bmad/README.md", "content": "# BMAD Pack\n\n[BMAD Method](https://github.com/bmad-code-org/BMAD-METHOD) is a document-first\ndelivery process: write a PRD, design the architecture, decompose it into\nepics and stories, gate on implementation readiness, implement story by story,\nthen run adversarial code review. This pack vendors the BMAD Method skills and\nruns that process as a durable Gas City build factory: every stage is a bead in\na graph that Gas City can retry, observe, and resume, and the result is a\nreviewed implementation in your repository with a full artifact trail.\n\n## When to choose bmad\n\n- You want disciplined, document-first delivery: an explicit PRD and\n architecture document exist before any code, and an implementation-readiness\n gate blocks coding until the stories are actually ready.\n- You want per-story rigor: each story gets its own implement, self-check,\n acceptance-audit, and fix loop, followed by a four-lane adversarial code\n review (blind-hunter, edge-case, acceptance-audit, gap-analysis).\n- Prefer `build-basic` (in the `gascity` pack) when you want the default\n starter factory with fewer stages and a single-lane review.\n- Prefer `compound-engineering` when review breadth matters most (20+\n specialist reviewer lanes), or `superpowers` when you want hard spec-approval\n gates and strict per-task TDD.\n\n## Quick start\n\nPrerequisites: Gas City installed and a city running, plus a git repository\nregistered as a rig. If you have not done that yet:\n\n```sh\nbrew install gascity\ngc init ~/my-city\ncd ~/my-city\ngc start\n```\n\n```sh\nmkdir proj && cd proj && git init\ngc rig add .\n```\n\n1. **Import the pack.** From the city directory, add bmad at city scope. This\n writes the import, fetches the latest release, and pins it in `packs.lock`\n — no clone needed. The pack imports the Gas City base pack internally as\n `gc`, so `build-base` and the `gc.*` formula surface are available\n transitively:\n\n ```sh\n gc import add https://github.com/gastownhall/gascity-packs.git//bmad\n ```\n\n2. **Import the rig roles in `city.toml`.** The rig additionally needs the\n `gascity/roles` import so the `gc.*` role agents (operator, publisher,\n and friends) can run work inside the target repository; run\n `gc import install` after editing:\n\n ```toml\n [[rigs]]\n name = \"proj\"\n\n [rigs.imports.gc]\n source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\"\n ```\n\n Contributors working on the packs themselves can clone\n `https://github.com/gastownhall/gascity-packs` and point either `source`\n at the local path (for example `../gascity-packs/bmad`) instead.\n\n3. **Launch your first build.** `bmad-build` is targeted\n (`target_required = true`), so create a bead describing the goal first,\n then sling the formula at it from the target rig context:\n\n ```sh\n gc bd create \"Add a --json flag to the export command\"\n gc sling gc.run-operator --on bmad-build \\\n --var artifact_root=plans/json-flag/build \\\n --var drain_policy=separate\n ```\n\n `artifact_root` is the only required variable; everything else has a\n default (see Customization below).\n\n4. **What happens next.** The workflow walks these stages in order: prepare,\n PRD requirements, architecture plan, architecture handoff review,\n epics/stories decomposition, implementation readiness, the story\n implementation drain, adversarial code review, finalize, publish. Stage\n artifacts (PRD, architecture document, decomposition, readiness report,\n review reports, final summary) land under `artifact_root` in your rig.\n\n5. **Watch progress.** The workflow root bead carries the run state and the\n `gc.build.*` handoff metadata; list beads with `gc bd --rig ` and\n convoys with `gc convoy --rig `. The default `interaction_mode` is\n `interactive`, so the run halts for your input at BMAD checkpoints — each\n halt records a resume note, and `gc session attach ` drops you\n into the waiting session. Pass `--var interaction_mode=autonomous` if you\n would rather it run unattended.\n\n## Stage map\n\n`bmad-build` extends `build-base` and keeps the inherited anchor order\n`prepare -> requirements -> plan -> plan-review -> decompose ->\nimplement/implement-same-session -> review -> finalize -> publish`. The pack\noverrides `requirements` (PRD), `plan` (architecture), `plan-review`\n(architecture handoff review), `decompose` (epics and stories),\n`implement`/`implement-same-session` (story-development drains), and `review`\n(adversarial code-review loop); `prepare`, `finalize`, and `publish` stay\ninherited. `implementation-readiness` is the only pack-added step: it needs\n`decompose`, both implementation drains need it, and its gate is the readiness\nreview run by `bmad.readiness-reviewer`, which records the readiness report\npath and outcome on the workflow root bead before implementation begins. No\nbase anchor is renamed, skipped, or reordered.\n\nThe native stage formulas extend the matching base methodology contracts:\n`bmad-planning` (`planning-base`), `bmad-decomposition`\n(`decomposition-base`), `bmad-implementation` (`implement`), `bmad-review`\n(`code-review-base`), and `bmad-fix-loop` (`fix-loop-base`). `bmad-build`\npins them as its selector defaults (`planning_formula`,\n`decomposition_formula`, `implementation_formula`,\n`implementation_item_formula` `bmad-story-development-item`,\n`code_review_formula`, `review_fix_formula`) with `implementation_target`\ndefaulting to `bmad.story-implementer`.\n\nSupported modes and drain policies, as declared in\n`[metadata.gc.methodology]`:\n\n| Axis | Values | Default | Meaning |\n| ---- | ------ | ------- | ------- |\n| `interaction_mode` | `interactive`, `autonomous`, `headless` | `interactive` | BMAD's menu/checkpoint axis: interactive runs preserve halts and user choices; headless automation expects all required inputs up front. |\n| `review_mode` | `report`, `agent`, `interactive` | `agent` | `report` synthesizes findings without applying fixes; `agent` and `interactive` feed the fix loop until the approval check passes. |\n| `drain_policy` | `separate`, `same-session` | `separate` | `separate` drains `bmad-story-development` item formulas in parallel worktrees with exclusive member access; `same-session` drains `bmad-story-development-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`. |\n\n```mermaid\nflowchart TD\n classDef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827;\n classDef bmad fill:#ecfdf5,stroke:#059669,color:#111827;\n classDef infra fill:#fff7ed,stroke:#d97706,color:#111827;\n classDef terminal fill:#f9fafb,stroke:#6b7280,color:#111827;\n\n Start[\"Launch bmad-build\"]:::terminal --> Prepare[\"prepare
inherited build-base\"]:::inherited\n Prepare --> PRD[\"requirements
BMAD PRD override\"]:::bmad\n PRD --> Architecture[\"plan
BMAD architecture override\"]:::bmad\n Architecture --> ArchReview[\"plan-review
BMAD architecture handoff override\"]:::bmad\n ArchReview --> Decompose[\"decompose
BMAD epics and stories override\"]:::bmad\n Decompose --> Readiness[\"implementation-readiness
BMAD readiness gate override\"]:::bmad\n Readiness --> DrainChoice{\"drain policy
Gas City drain\"}:::infra\n DrainChoice -->|separate| DrainSeparate[\"separate drain
parallel by convoy deps\"]:::infra\n DrainChoice -->|same-session| DrainShared[\"same-session drain
single worker lane\"]:::infra\n\n subgraph StoryFormula[\"bmad-story-development item formula: one formula instance per story bead\"]\n direction TB\n SeparatePrep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> StoryLoopStart[\"run BMAD story development
override control step\"]:::bmad\n SharedStoryStart[\"run BMAD shared story development
override implement-item\"]:::bmad\n StoryLoopStart --> StorySetup[\"setup-bmad-story-development\"]:::bmad\n SharedStoryStart --> StorySetup\n StorySetup --> ImplementStory[\"implement-story\"]:::bmad\n ImplementStory --> SelfCheck[\"story-self-check\"]:::bmad\n SelfCheck --> AcceptanceAudit[\"acceptance-audit\"]:::bmad\n AcceptanceAudit --> ApplyStory[\"apply-story-findings\"]:::bmad\n ApplyStory --> StoryGate{\"story clean?
implementation-review check\"}:::infra\n StoryGate -->|no: fix story and re-check| StorySetup\n StoryGate -->|yes| StoryDone[\"story accepted\"]:::bmad\n StoryDone --> CloseStory[\"close source anchor
inherited do-work
separate drain only\"]:::inherited\n end\n\n DrainSeparate --> SeparatePrep\n DrainShared --> SharedStoryStart\n CloseStory --> DrainFanIn[\"fan in
drain waits for all item roots\"]:::infra\n StoryDone --> DrainFanIn\n\n DrainFanIn --> ReviewSetup[\"review
BMAD code-review override\"]:::bmad\n\n subgraph CodeReview[\"post-implementation review override: BMAD review loop\"]\n direction TB\n ReviewSetup --> GatherContext[\"gather-bmad-review-context\"]:::bmad\n GatherContext --> ReviewFanout[\"fan out BMAD review lanes\"]:::infra\n\n subgraph ReviewLanes[\"parallel reviewer lanes: sibling beads with no needs between them\"]\n direction LR\n BlindHunter[\"blind-hunter-review\"]:::bmad\n EdgeCase[\"edge-case-review\"]:::bmad\n AcceptanceReview[\"acceptance-auditor-review\"]:::bmad\n GapReview[\"gap-analysis-review\"]:::bmad\n end\n\n ReviewFanout --> BlindHunter\n ReviewFanout --> EdgeCase\n ReviewFanout --> AcceptanceReview\n ReviewFanout --> GapReview\n BlindHunter --> ReviewFanIn[\"fan in
wait for all review artifacts\"]:::infra\n EdgeCase --> ReviewFanIn\n AcceptanceReview --> ReviewFanIn\n GapReview --> ReviewFanIn\n ReviewFanIn --> ReviewSynth[\"synthesize-bmad-review\"]:::bmad\n ReviewSynth --> ApplyReview[\"apply-bmad-review-findings\"]:::bmad\n ApplyReview --> ReviewGate{\"implementation approved?
graph check\"}:::infra\n ReviewGate -->|no: fix and re-review| ReviewFanout\n ReviewGate -->|yes| ReviewDone[\"finalize BMAD code review\"]:::bmad\n end\n\n ReviewDone --> Finalize[\"finalize
inherited build-base\"]:::inherited\n Finalize --> Publish[\"publish
inherited build-base\"]:::inherited\n Publish --> Done[\"workflow complete\"]:::terminal\n```\n\nBlue nodes are inherited Gas City behavior, green nodes are BMAD-specific\noverrides, and amber nodes are Gas City graph, convoy, or drain infrastructure.\nThe story-development item formula loops per implementation bead until the\nstory self-check and acceptance audit are clean. After the convoy drains, the\nBMAD code-review loop fans out adversarial review lanes, synthesizes findings,\napplies required fixes, and repeats until the implementation-review check\npasses. Those reviewer lanes are real sibling beads — the fan-in barrier in\nthe diagram documents the synthesizer's `needs` list, not a serial execution\nstep between reviewers.\n\n## Customization\n\nLaunch variables you are most likely to set (all passed as `--var k=v`):\n\n| Variable | Default | What it changes |\n| -------- | ------- | --------------- |\n| `artifact_root` | required | Directory in the rig where all stage artifacts are written. |\n| `interaction_mode` | `interactive` | Human participation: `interactive` preserves BMAD halts and menus, `autonomous` makes reasonable decisions while recording evidence, `headless` is for automation with no blocking prompts. |\n| `review_mode` | `agent` | Review authority: `report` writes findings only; `agent`/`interactive` apply fixes through the fix loop until approval. |\n| `drain_policy` | `separate` | `separate` runs stories in parallel worktrees; `same-session` runs them sequentially in one shared session. |\n| `implementation_target` | `bmad.story-implementer` | Role agent that implements stories and applies review fixes. |\n| `push` | `false` | Set `true` to allow the final publish stage to push after all checks pass. |\n| `open_pr` | `false` | Set `true` to allow the final publish stage to open a PR after all checks pass. |\n| `max_iterations` | `10` | Maximum implementation/review fix attempts before the loop gives up. |\n| `planning_formula`, `decomposition_formula`, `implementation_formula`, `implementation_item_formula`, `code_review_formula`, `review_fix_formula` | `bmad-planning`, `bmad-decomposition`, `bmad-implementation`, `bmad-story-development-item`, `bmad-review`, `bmad-fix-loop` | Selector surface: swap any stage methodology for another formula that satisfies the matching base contract. |\n\nTo change what a stage tells its agent without touching the graph, shadow the\nstage prompt asset: put a Markdown file at the same relative path in a\nhigher-priority city or local pack layer. For example, to replace the PRD\nstage prompt, create `assets/workflows/bmad-build/requirements.md` in your\ncity assets — Gas City resolves `description_file` assets through the normal\nimport/layer search path, so your file replaces this pack's prompt text. Every\nstage body under `bmad/assets/workflows/` can be shadowed the same way. For\nadvanced step overrides (replacing a step with another formula, expansion, or\nfanout), see \"Stable Workflow Override Interface\" in the\n[gascity pack README](../gascity/README.md).\n\n## Examples\n\nInteractive first feature build — the default posture. Halts at BMAD\ncheckpoints for your decisions, runs stories in parallel worktrees, and stops\nbefore any push or PR:\n\n```sh\ngc bd create \"Add a --json flag to the export command\"\ngc sling gc.run-operator --on bmad-build \\\n --var artifact_root=plans/json-flag/build \\\n --var drain_policy=separate\n```\n\nAutonomous run that ships — no blocking prompts, review findings fixed by the\nagent loop, and publish allowed to push the branch and open a PR once every\ncheck passes:\n\n```sh\ngc bd create \"Migrate the config loader from JSON to TOML\"\ngc sling gc.run-operator --on bmad-build \\\n --var artifact_root=plans/toml-config/build \\\n --var interaction_mode=autonomous \\\n --var review_mode=agent \\\n --var push=true \\\n --var open_pr=true\n```\n\nSame-session drain for a small task — all stories run sequentially in one\nshared worker session and worktree instead of fanning out, which suits small\nchanges where parallel worktrees are overhead:\n\n```sh\ngc bd create \"Fix off-by-one in pagination cursor\"\ngc sling gc.run-operator --on bmad-build \\\n --var artifact_root=plans/pagination-fix/build \\\n --var interaction_mode=autonomous \\\n --var drain_policy=same-session\n```\n\n## What's vendored\n\n- Formula: `bmad-build`\n- Methodology formulas: `bmad-planning`, `bmad-decomposition`,\n `bmad-implementation`, `bmad-review`, and `bmad-fix-loop`\n- Expansion formulas: `bmad-code-review-flow`\n- Implementation item formulas: `bmad-story-development`,\n `bmad-story-development-item`\n- Pack agents: `bmad.prd-writer`, `bmad.architect`,\n `bmad.epic-story-decomposer`, `bmad.readiness-reviewer`,\n `bmad.story-implementer`, `bmad.story-self-checker`,\n `bmad.acceptance-auditor`, `bmad.blind-hunter-reviewer`,\n `bmad.edge-case-reviewer`, and `bmad.bmad-review-synthesizer`\n- Vendored skills: `bmad-prd`, `bmad-create-architecture`,\n `bmad-check-implementation-readiness`, `bmad-create-epics-and-stories`,\n `bmad-quick-dev`, `bmad-dev-story`, `bmad-code-review`,\n `bmad-brainstorming`, and `bmad-spec`\n- Provenance: `vendor/bmad-method/upstream.toml` records the upstream\n repository, pinned commit, MIT license, and vendored paths\n\nBMAD quick-dev and code-review describe sub-agent/task handoffs and parallel\nreview layers. This pack converts those shapes into Gas City item formulas and\nexpansion formulas with explicit implementation, self-check, acceptance-audit,\nadversarial review, synthesis, and fix lanes. The vendored skill files are\nsource material only; the workflow must not invoke provider-native subagents,\nslash commands, task tools, or the upstream plugin runtime.\n\nBMAD structured steps are preserved as graph structure wherever possible. The\npack keeps BMAD's step-file discipline as prompt guidance, but turns repeated\nimplementation and review handoffs into formulas, check loops, and fanout lanes\nso the work is durable, resumable, and visible in Gas City. When BMAD text says\nto launch a task or review subagent, read that as a request for a Gas City lane\nor expansion child.\n\n## Compatibility ledger\n\nThe pack-local compatibility ledger lives at\n[`bmad/REQUIREMENTS.md`](./REQUIREMENTS.md) and records the build-base\ncontract proofs — the inherited `gc` import, preserved anchor order, mode and\ndrain declarations, selector defaults, and the evidence commands that\nreproduce each claim." }, "og_image": "/og/bmad.svg", - "latest": "0.1.0", + "latest": "0.1.1", "releases": [ { "version": "0.1.0", @@ -41,6 +41,14 @@ "hash": "sha256:cb81d9e35fe4ca2263922e901eac34aec6183d92832aa0695e43ff038462087d", "description": "First registry release of the BMAD build factory.", "withdrawn": false + }, + { + "version": "0.1.1", + "ref": "main", + "commit": "390cac1a8a0478b6dfde603dae00a43ed9748d13", + "hash": "sha256:f59ebe28181a208086a7018d1c6df4396894c22be9bd272e025312feb7879a0a", + "description": "Implementer commits in the source-anchor worktree (only real changes); code review records a validated review report on the workflow root; apply-findings keeps artifacts schema-valid; picks up newer gascity pack commits (do-work now needs an origin remote).", + "withdrawn": false } ] }, @@ -80,13 +88,13 @@ "description": "Every Inc.'s Compound Engineering methodology as a Gas City build factory, with the widest reviewer-persona fanout in the catalog.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/compound-engineering", "source_kind": "git", - "search_text": "compound-engineering gascity-packs maintained gas city every inc.'s compound engineering methodology as a gas city build factory, with the widest reviewer-persona fanout in the catalog. https://github.com/gastownhall/gascity-packs/tree/main/compound-engineering git # compound engineering pack this pack runs [every inc.'s compound engineering](https://github.com/everyinc/compound-engineering-plugin) methodology as a gas city build factory: brainstormed requirements, a written plan that a persona panel reviews before any code exists, parallel implementation, the widest reviewer fanout of any pack in this repository, and a compounding step that turns each build's findings into durable learnings. you import one pack, launch one formula (`compound-build`), and get the full lifecycle with retries, persistence, and observable per-stage artifacts. ## when to choose compound-engineering - **review depth matters most.** the post-implementation review fans out to up to 17 specialist lanes (correctness, testing, security, performance, data migration, api contracts, and more), versus `build-basic`'s single review lane. - **you want plans reviewed before code.** a four-persona plan review loop (coherence, feasibility, scope, architecture) must approve the plan before decomposition starts. - **you want learnings to compound.** a learnings-research lane feeds past findings into each review, and the finalize stage distills new learnings via the vendored `ce-compound` skill. - **pick a different pack when...** you want strict per-task tdd with approval gates (`superpowers`), document-first story decomposition (`bmad`), founder/pm-flavored qa and release gates (`gstack`), or the fewest moving parts (`build-basic` in the base `gascity` pack). ## quick start prerequisites: git, and a repository you want agents to work on. 1. **install gas city and start a city** (skip anything you have already done): ```sh brew install gascity gc init ~/my-city cd ~/my-city gc start ``` 2. **register your repository as a rig.** a *rig* is a repo gas city agents can work in: ```sh cd ~/your-project # any git repo; `git init` a fresh one if needed gc rig add . ``` 3. **import the pack.** from the city directory, add `compound-engineering` at city scope. this writes the import, fetches the latest release, and pins it in `packs.lock` — no clone needed. the pack imports the gas city base pack internally as `gc`, so the `build-base` contract and `gc.*` formulas come along transitively: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//compound-engineering ``` 4. **import the rig roles in `city.toml`.** each rig that should run work also needs the `gascity/roles` import, which provides the worker role agents (`gc.run-operator`, `gc.implementation-worker`, and friends) that formulas route to; run `gc import install` after editing: ```toml [[rigs]] name = \"your-project\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/compound-engineering`) instead. 5. **verify the formula is visible** from the rig context: ```sh gc formula catalog --json gc formula show compound-build --json ``` 6. **launch your first build.** `compound-build` is a targeted formula (`target_required = true`), so create a bead describing the goal and sling the workflow at it. launch from the target rig context (or pass your normal `--rig ` selection): ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` 7. **watch it run.** the build walks `prepare -> requirements -> plan -> plan-review -> decompose -> implement -> review -> finalize -> publish`. each stage writes its artifact (requirements, plan, plan-review report, per-item implementation summaries, code-review report, final report) under `artifact_root` in your rig as it closes, and records the path in `gc.build.*` metadata on the workflow root bead. the build is done when the finalize stage records `gc.build.final_report_path` and publish completes (a no-op unless you set `push`/`open_pr`). ## stage map `compound-build` declares `extends = [\"build-base\"]` and overrides stages of the inherited contract under their base ids; no anchor is renamed, skipped, or reordered. | build-base stage | compound-build behavior | | ---------------- | ----------------------- | | `prepare` | inherited from `build-base`. | | `requirements` | vendored `ce-brainstorm` skill writes the requirements artifact. | | `plan` | vendored `ce-plan` skill writes the plan artifact. | | `plan-review` | expands `compound-plan-review`: coherence, feasibility, scope-guardian, and architecture-strategist lanes fan out, a synthesizer fans in, `ce-plan` applies findings, and the loop repeats (up to 6 passes) until approved. | | `decompose` | inherited `build-base` task-graph decomposition. | | `implement` / `implement-same-session` | gas city drain. `separate` drains `compound-work` item formulas in parallel with exclusive member access; `same-session` drains `compound-work-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`. both run the vendored `ce-work` role per item. | | `review` | expands `compound-code-review`: the full reviewer-persona fanout, synthesis, and an apply-fix lane, looping (up to 8 passes) until approved. | | `finalize` | expands `compound-resolution`: `inventory-artifacts -> review-resolution` (`ce-pr-comment-resolver`) `-> synthesize-resolution` (`ce-compound` learnings distillation). | | `publish` | inherited from `build-base`; gated by `push` and `open_pr`. | supported postures, declared in `[metadata.gc.methodology]`: - `interaction_modes`: `interactive`, `autonomous`, `headless` - `review_modes`: `report`, `agent`, `interactive` - `allowed_drain_policies`: `separate`, `same-session` (implementation strategy is `drain`) for mode vocabulary, treat `interaction_mode` as the planning and human-gate axis, and `review_mode` as the review authority axis. compound's raw `mode:agent` maps to machine handoff/report behavior, while default interactive review maps to a direct build where the reviewer is allowed to own safe fix application. ```mermaid flowchart td classdef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef compound fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch compound-build\"]:::terminal --> prepare[\"prepare
inherited build-base\"]:::inherited prepare --> requirements[\"requirements
ce-brainstorm override\"]:::compound requirements --> plan[\"plan
ce-plan override\"]:::compound subgraph planreview[\"plan-review override: compound persona review loop\"] direction tb plan --> plansetup[\"setup plan-review context
compound override\"]:::compound plansetup --> planfanout[\"fan out plan persona lanes\"]:::infra planfanout --> coherence[\"coherence-review\"]:::compound planfanout --> feasibility[\"feasibility-review\"]:::compound planfanout --> scope[\"scope-review\"]:::compound planfanout --> architecture[\"architecture-review\"]:::compound coherence --> plansynth[\"synthesize-plan-review\"]:::compound feasibility --> plansynth scope --> plansynth architecture --> plansynth plansynth --> applyplan[\"apply-plan-findings\"]:::compound applyplan --> plangate{\"plan approved?
graph check\"}:::infra plangate -->|no: next review pass| planfanout plangate -->|yes| planreviewdone[\"finalize approved plan review\"]:::compound end planreviewdone --> decompose[\"decompose
inherited build-base task graph\"]:::inherited decompose --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra subgraph itemformula[\"compound-work item formulas: one formula instance per implementation bead\"] direction tb separateprep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> compoundwork[\"ce-work implement override\"]:::compound sharedwork[\"ce-work shared implement-item override\"]:::compound compoundwork --> closeitem[\"close source anchor
inherited do-work\"]:::inherited end drainseparate --> separateprep drainshared --> sharedwork closeitem --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra sharedwork --> drainfanin drainfanin --> codesetup[\"review setup
compound code-review override\"]:::compound subgraph codereview[\"post-implementation review override: compound reviewer loop\"] direction tb codesetup --> codeselector[\"select conditional reviewer lanes
ce cheap selector\"]:::compound codeselector --> codefanout[\"fan out always-on lanes
and conditional gates\"]:::infra subgraph codelanes[\"parallel reviewer lanes: sibling beads with no needs between them\"] direction lr correctness[\"correctness\"]:::compound testing[\"testing\"]:::compound maintainability[\"maintainability\"]:::compound standards[\"project standards\"]:::compound agentnative[\"agent-native\"]:::compound learnings[\"learnings research\"]:::compound conditionalgates[\"cheap gates
close skipped lanes\"]:::infra crosscutting[\"selected cross-cutting
security, performance, api,
data, reliability, adversarial,
previous comments\"]:::compound stackspecific[\"selected stack-specific
frontend races, swift ios,
deployment verification\"]:::compound gapanalysis[\"gap analysis\"]:::compound end codefanout --> correctness codefanout --> testing codefanout --> maintainability codefanout --> standards codefanout --> agentnative codefanout --> learnings codefanout --> conditionalgates conditionalgates -->|selected| crosscutting conditionalgates -->|selected| stackspecific conditionalgates -->|skipped: no-op artifact| codefanin codefanout --> gapanalysis correctness --> codefanin[\"fan in
wait for all review artifacts\"]:::infra testing --> codefanin maintainability --> codefanin standards --> codefanin agentnative --> codefanin learnings --> codefanin crosscutting --> codefanin stackspecific --> codefanin gapanalysis --> codefanin codefanin --> codesynth[\"synthesize-code-review\"]:::compound codesynth --> applyreview[\"apply-review-findings\"]:::compound applyreview --> codegate{\"implementation approved?
graph check\"}:::infra codegate -->|no: fix and re-review| codefanout codegate -->|yes| codereviewdone[\"finalize approved code review\"]:::compound end codereviewdone --> resolutionstart[\"finalize
compound-resolution override\"]:::compound subgraph resolution[\"compound-resolution finalization\"] direction tb resolutionstart --> inventory[\"inventory-artifacts\"]:::compound inventory --> reviewresolution[\"review-resolution
pr/comment resolver\"]:::compound reviewresolution --> synthesizeresolution[\"synthesize-resolution
ce-compound\"]:::compound synthesizeresolution --> finalize[\"finalize build result\"]:::compound end finalize --> publish[\"publish
inherited build-base\"]:::inherited publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are inherited gas city behavior, green nodes are compound engineering-specific overrides, and amber nodes are gas city graph, convoy, or drain infrastructure. the plan-review and code-review stages are explicit graph loops: required findings route back through the same fanout/fix stage instead of falling through to decomposition or finalization. implementation keeps the gas city drain lifecycle, so independent convoy members can run in parallel while each member receives a compound `ce-work` item formula. ## customization all knobs are launch vars on `compound-build` (most are inherited from `build-base`); pass them as `--var key=value`: | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | required | directory under the rig where every stage artifact is written. | | `interaction_mode` | `interactive` | human participation in planning and gates: `interactive` keeps blocking questions and approval menus, `autonomous` decides and records evidence, `headless` never blocks. | | `review_mode` | `agent` | review authority: `report` writes findings only, `agent` is a structured machine handoff whose caller applies fixes, `interactive` lets the review apply safe fixes directly. | | `drain_policy` | `separate` | `separate` runs items in parallel worktrees; `same-session` runs them serially in one shared session. | | `implementation_target` | `compound-engineering.ce-work` | role that implements each drained item and the apply-fix lanes. | | `push` | `false` | allow publish to push after all checks pass. | | `open_pr` | `false` | allow publish to open a pr after all checks pass. | | `max_iterations` | `10` | maximum implementation/review fix attempts. | | `planning_formula` | `compound-planning` | methodology selector adapters use to delegate planning. | | `decomposition_formula` | `compound-decomposition` | methodology selector for task creation. | | `implementation_formula` | `compound-implementation` | methodology selector for the implementation entry. | | `implementation_item_formula` | `compound-work-item` | item formula for shared-drain adapters. | | `code_review_formula` | `compound-review` | methodology selector for standalone code review. | | `review_fix_formula` | `compound-fix-loop` | methodology selector for the standalone review-fix loop. | the selector vars are how adapters such as the `build-from-*` continuation formulas pick the compound implementations of each base methodology contract without overriding any formula. `context_path`, `requirements_path`, `plan_path`, and `decomposition_path` (inherited from `build-base`) let you reuse artifacts that already exist. **prompt customization by asset shadowing.** every step body is a markdown asset at `assets/workflows//.md`, resolvable through the normal import/layer search path. to inject repository-specific security review policy, place a file at `assets/workflows/compound-code-review/{target}.security-review.md` in a higher-priority city or local pack layer (the `{target}.` prefix is literal in the filename); to reshape planning, shadow `assets/workflows/compound-build/plan.md`. the shadowing file replaces the prompt text without changing the formula graph. **review lane selection and extension.** the always-on lanes (correctness, testing, maintainability, project standards, agent-native, learnings research, gap analysis) run as sibling beads on every pass. conditional cross-cutting lanes (security, performance, api contract, data migration, reliability, adversarial, previous comments) and stack-specific lanes (frontend races, swift ios, deployment verification) each sit behind a cheap selector gate run by `ce-code-review-selector`; skipped gates close their lane with a no-op artifact so the synthesis fan-in never blocks. to add a lane, copy the `compound-code-review` expansion into a higher-priority formula layer, add the lane (gated or not), and include it in the `synthesize-code-review` `needs` list. see \"stable workflow override interface\" in [`gascity/readme.md`](../gascity/readme.md) for the compatibility rules both customization modes must follow. compound review fanout is the pack's main showcase. the raw `ce-code-review` persona roster becomes sibling review beads with selector gates, synthesis, and an apply-fix lane. use report-only adapter runs for github pr review or other external comment workflows. use interactive direct builds when you want the raw compound feel: planning prompts, full review context, and review behavior that can apply safe fixes when the methodology permits it. ## examples interactive first feature build — the default posture. planning asks blocking questions, gates wait for approval, and review may apply safe fixes: ```sh gc bd create \"add rate limiting to the public api\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/rate-limiting/build \\ --var interaction_mode=interactive \\ --var review_mode=interactive \\ --var drain_policy=separate ``` autonomous run that ships — no blocking prompts; decisions are recorded as evidence, and publish pushes the branch and opens a pr after all checks pass: ```sh gc bd create \"migrate config parsing from ini to toml\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/toml-config/build \\ --var interaction_mode=autonomous \\ --var review_mode=agent \\ --var drain_policy=separate \\ --var push=true \\ --var open_pr=true ``` report-only review posture — review lanes produce findings without applying fixes, the adapter shape for feeding an external comment workflow such as github pr review: ```sh gc bd create \"harden webhook signature verification\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/webhook-hardening/build \\ --var interaction_mode=headless \\ --var review_mode=report ``` ## what's vendored - formula: `compound-build` - expansion formulas: `compound-plan-review`, `compound-code-review`, `compound-resolution` - methodology selector formulas: `compound-planning`, `compound-decomposition`, `compound-implementation`, `compound-review`, `compound-fix-loop` - implementation item formulas: `compound-work`, `compound-work-item` - vendored skills: `ce-brainstorm`, `ce-plan`, `ce-work`, `ce-code-review`, and `ce-compound` - vendored agent personas under `vendor/compound-engineering-plugin/agents/`, adapted as the pack-local `ce-*` agents - provenance: `vendor/compound-engineering-plugin/upstream.toml` records the upstream repository, pinned commit, and mit license upstream compound engineering skills use persona subagents. this pack converts those fanouts into gas city item formulas and expansion formulas with explicit `gc.*` lanes. the vendored agent files are prompt inputs only; the workflow must not invoke provider-native subagents, slash commands, task tools, or the upstream plugin runtime. `ce-work` is an override of the inherited `do-work` implementation step; `ce-compound` is used during the `finalize` stage through `compound-resolution`; the base workflow does not add a separate compound stage. ## compatibility ledger the pack-local compatibility ledger lives at [`compound-engineering/requirements.md`](./requirements.md) and records the build-base contract proofs, including the inherited `gc` import, preserved anchor order, mode and drain declarations, persona-lane routing, and the evidence commands that reproduce each claim. 0.1.0 first registry release of the compound engineering build factory.", + "search_text": "compound-engineering gascity-packs maintained gas city every inc.'s compound engineering methodology as a gas city build factory, with the widest reviewer-persona fanout in the catalog. https://github.com/gastownhall/gascity-packs/tree/main/compound-engineering git # compound engineering pack this pack runs [every inc.'s compound engineering](https://github.com/everyinc/compound-engineering-plugin) methodology as a gas city build factory: brainstormed requirements, a written plan that a persona panel reviews before any code exists, parallel implementation, the widest reviewer fanout of any pack in this repository, and a compounding step that turns each build's findings into durable learnings. you import one pack, launch one formula (`compound-build`), and get the full lifecycle with retries, persistence, and observable per-stage artifacts. ## when to choose compound-engineering - **review depth matters most.** the post-implementation review fans out to up to 17 specialist lanes (correctness, testing, security, performance, data migration, api contracts, and more), versus `build-basic`'s single review lane. - **you want plans reviewed before code.** a four-persona plan review loop (coherence, feasibility, scope, architecture) must approve the plan before decomposition starts. - **you want learnings to compound.** a learnings-research lane feeds past findings into each review, and the finalize stage distills new learnings via the vendored `ce-compound` skill. - **pick a different pack when...** you want strict per-task tdd with approval gates (`superpowers`), document-first story decomposition (`bmad`), founder/pm-flavored qa and release gates (`gstack`), or the fewest moving parts (`build-basic` in the base `gascity` pack). ## quick start prerequisites: git, and a repository you want agents to work on. 1. **install gas city and start a city** (skip anything you have already done): ```sh brew install gascity gc init ~/my-city cd ~/my-city gc start ``` 2. **register your repository as a rig.** a *rig* is a repo gas city agents can work in: ```sh cd ~/your-project # any git repo; `git init` a fresh one if needed gc rig add . ``` 3. **import the pack.** from the city directory, add `compound-engineering` at city scope. this writes the import, fetches the latest release, and pins it in `packs.lock` — no clone needed. the pack imports the gas city base pack internally as `gc`, so the `build-base` contract and `gc.*` formulas come along transitively: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//compound-engineering ``` 4. **import the rig roles in `city.toml`.** each rig that should run work also needs the `gascity/roles` import, which provides the worker role agents (`gc.run-operator`, `gc.implementation-worker`, and friends) that formulas route to; run `gc import install` after editing: ```toml [[rigs]] name = \"your-project\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/compound-engineering`) instead. 5. **verify the formula is visible** from the rig context: ```sh gc formula catalog --json gc formula show compound-build --json ``` 6. **launch your first build.** `compound-build` is a targeted formula (`target_required = true`), so create a bead describing the goal and sling the workflow at it. launch from the target rig context (or pass your normal `--rig ` selection): ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` 7. **watch it run.** the build walks `prepare -> requirements -> plan -> plan-review -> decompose -> implement -> review -> finalize -> publish`. each stage writes its artifact (requirements, plan, plan-review report, per-item implementation summaries, code-review report, final report) under `artifact_root` in your rig as it closes, and records the path in `gc.build.*` metadata on the workflow root bead. the build is done when the finalize stage records `gc.build.final_report_path` and publish completes (a no-op unless you set `push`/`open_pr`). ## stage map `compound-build` declares `extends = [\"build-base\"]` and overrides stages of the inherited contract under their base ids; no anchor is renamed, skipped, or reordered. | build-base stage | compound-build behavior | | ---------------- | ----------------------- | | `prepare` | inherited from `build-base`. | | `requirements` | vendored `ce-brainstorm` skill writes the requirements artifact. | | `plan` | vendored `ce-plan` skill writes the plan artifact. | | `plan-review` | expands `compound-plan-review`: coherence, feasibility, scope-guardian, and architecture-strategist lanes fan out, a synthesizer fans in, `ce-plan` applies findings, and the loop repeats (up to 6 passes) until approved. | | `decompose` | inherited `build-base` task-graph decomposition. | | `implement` / `implement-same-session` | gas city drain. `separate` drains `compound-work` item formulas in parallel with exclusive member access; `same-session` drains `compound-work-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`. both run the vendored `ce-work` role per item. | | `review` | expands `compound-code-review`: the full reviewer-persona fanout, synthesis, and an apply-fix lane, looping (up to 8 passes) until approved. | | `finalize` | expands `compound-resolution`: `inventory-artifacts -> review-resolution` (`ce-pr-comment-resolver`) `-> synthesize-resolution` (`ce-compound` learnings distillation). | | `publish` | inherited from `build-base`; gated by `push` and `open_pr`. | supported postures, declared in `[metadata.gc.methodology]`: - `interaction_modes`: `interactive`, `autonomous`, `headless` - `review_modes`: `report`, `agent`, `interactive` - `allowed_drain_policies`: `separate`, `same-session` (implementation strategy is `drain`) for mode vocabulary, treat `interaction_mode` as the planning and human-gate axis, and `review_mode` as the review authority axis. compound's raw `mode:agent` maps to machine handoff/report behavior, while default interactive review maps to a direct build where the reviewer is allowed to own safe fix application. ```mermaid flowchart td classdef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef compound fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch compound-build\"]:::terminal --> prepare[\"prepare
inherited build-base\"]:::inherited prepare --> requirements[\"requirements
ce-brainstorm override\"]:::compound requirements --> plan[\"plan
ce-plan override\"]:::compound subgraph planreview[\"plan-review override: compound persona review loop\"] direction tb plan --> plansetup[\"setup plan-review context
compound override\"]:::compound plansetup --> planfanout[\"fan out plan persona lanes\"]:::infra planfanout --> coherence[\"coherence-review\"]:::compound planfanout --> feasibility[\"feasibility-review\"]:::compound planfanout --> scope[\"scope-review\"]:::compound planfanout --> architecture[\"architecture-review\"]:::compound coherence --> plansynth[\"synthesize-plan-review\"]:::compound feasibility --> plansynth scope --> plansynth architecture --> plansynth plansynth --> applyplan[\"apply-plan-findings\"]:::compound applyplan --> plangate{\"plan approved?
graph check\"}:::infra plangate -->|no: next review pass| planfanout plangate -->|yes| planreviewdone[\"finalize approved plan review\"]:::compound end planreviewdone --> decompose[\"decompose
inherited build-base task graph\"]:::inherited decompose --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra subgraph itemformula[\"compound-work item formulas: one formula instance per implementation bead\"] direction tb separateprep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> compoundwork[\"ce-work implement override\"]:::compound sharedwork[\"ce-work shared implement-item override\"]:::compound compoundwork --> closeitem[\"close source anchor
inherited do-work\"]:::inherited end drainseparate --> separateprep drainshared --> sharedwork closeitem --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra sharedwork --> drainfanin drainfanin --> codesetup[\"review setup
compound code-review override\"]:::compound subgraph codereview[\"post-implementation review override: compound reviewer loop\"] direction tb codesetup --> codeselector[\"select conditional reviewer lanes
ce cheap selector\"]:::compound codeselector --> codefanout[\"fan out always-on lanes
and conditional gates\"]:::infra subgraph codelanes[\"parallel reviewer lanes: sibling beads with no needs between them\"] direction lr correctness[\"correctness\"]:::compound testing[\"testing\"]:::compound maintainability[\"maintainability\"]:::compound standards[\"project standards\"]:::compound agentnative[\"agent-native\"]:::compound learnings[\"learnings research\"]:::compound conditionalgates[\"cheap gates
close skipped lanes\"]:::infra crosscutting[\"selected cross-cutting
security, performance, api,
data, reliability, adversarial,
previous comments\"]:::compound stackspecific[\"selected stack-specific
frontend races, swift ios,
deployment verification\"]:::compound gapanalysis[\"gap analysis\"]:::compound end codefanout --> correctness codefanout --> testing codefanout --> maintainability codefanout --> standards codefanout --> agentnative codefanout --> learnings codefanout --> conditionalgates conditionalgates -->|selected| crosscutting conditionalgates -->|selected| stackspecific conditionalgates -->|skipped: no-op artifact| codefanin codefanout --> gapanalysis correctness --> codefanin[\"fan in
wait for all review artifacts\"]:::infra testing --> codefanin maintainability --> codefanin standards --> codefanin agentnative --> codefanin learnings --> codefanin crosscutting --> codefanin stackspecific --> codefanin gapanalysis --> codefanin codefanin --> codesynth[\"synthesize-code-review\"]:::compound codesynth --> applyreview[\"apply-review-findings\"]:::compound applyreview --> codegate{\"implementation approved?
graph check\"}:::infra codegate -->|no: fix and re-review| codefanout codegate -->|yes| codereviewdone[\"finalize approved code review\"]:::compound end codereviewdone --> resolutionstart[\"finalize
compound-resolution override\"]:::compound subgraph resolution[\"compound-resolution finalization\"] direction tb resolutionstart --> inventory[\"inventory-artifacts\"]:::compound inventory --> reviewresolution[\"review-resolution
pr/comment resolver\"]:::compound reviewresolution --> synthesizeresolution[\"synthesize-resolution
ce-compound\"]:::compound synthesizeresolution --> finalize[\"finalize build result\"]:::compound end finalize --> publish[\"publish
inherited build-base\"]:::inherited publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are inherited gas city behavior, green nodes are compound engineering-specific overrides, and amber nodes are gas city graph, convoy, or drain infrastructure. the plan-review and code-review stages are explicit graph loops: required findings route back through the same fanout/fix stage instead of falling through to decomposition or finalization. implementation keeps the gas city drain lifecycle, so independent convoy members can run in parallel while each member receives a compound `ce-work` item formula. ## customization all knobs are launch vars on `compound-build` (most are inherited from `build-base`); pass them as `--var key=value`: | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | required | directory under the rig where every stage artifact is written. | | `interaction_mode` | `interactive` | human participation in planning and gates: `interactive` keeps blocking questions and approval menus, `autonomous` decides and records evidence, `headless` never blocks. | | `review_mode` | `agent` | review authority: `report` writes findings only, `agent` is a structured machine handoff whose caller applies fixes, `interactive` lets the review apply safe fixes directly. | | `drain_policy` | `separate` | `separate` runs items in parallel worktrees; `same-session` runs them serially in one shared session. | | `implementation_target` | `compound-engineering.ce-work` | role that implements each drained item and the apply-fix lanes. | | `push` | `false` | allow publish to push after all checks pass. | | `open_pr` | `false` | allow publish to open a pr after all checks pass. | | `max_iterations` | `10` | maximum implementation/review fix attempts. | | `planning_formula` | `compound-planning` | methodology selector adapters use to delegate planning. | | `decomposition_formula` | `compound-decomposition` | methodology selector for task creation. | | `implementation_formula` | `compound-implementation` | methodology selector for the implementation entry. | | `implementation_item_formula` | `compound-work-item` | item formula for shared-drain adapters. | | `code_review_formula` | `compound-review` | methodology selector for standalone code review. | | `review_fix_formula` | `compound-fix-loop` | methodology selector for the standalone review-fix loop. | the selector vars are how adapters such as the `build-from-*` continuation formulas pick the compound implementations of each base methodology contract without overriding any formula. `context_path`, `requirements_path`, `plan_path`, and `decomposition_path` (inherited from `build-base`) let you reuse artifacts that already exist. **prompt customization by asset shadowing.** every step body is a markdown asset at `assets/workflows//.md`, resolvable through the normal import/layer search path. to inject repository-specific security review policy, place a file at `assets/workflows/compound-code-review/{target}.security-review.md` in a higher-priority city or local pack layer (the `{target}.` prefix is literal in the filename); to reshape planning, shadow `assets/workflows/compound-build/plan.md`. the shadowing file replaces the prompt text without changing the formula graph. **review lane selection and extension.** the always-on lanes (correctness, testing, maintainability, project standards, agent-native, learnings research, gap analysis) run as sibling beads on every pass. conditional cross-cutting lanes (security, performance, api contract, data migration, reliability, adversarial, previous comments) and stack-specific lanes (frontend races, swift ios, deployment verification) each sit behind a cheap selector gate run by `ce-code-review-selector`; skipped gates close their lane with a no-op artifact so the synthesis fan-in never blocks. to add a lane, copy the `compound-code-review` expansion into a higher-priority formula layer, add the lane (gated or not), and include it in the `synthesize-code-review` `needs` list. see \"stable workflow override interface\" in [`gascity/readme.md`](../gascity/readme.md) for the compatibility rules both customization modes must follow. compound review fanout is the pack's main showcase. the raw `ce-code-review` persona roster becomes sibling review beads with selector gates, synthesis, and an apply-fix lane. use report-only adapter runs for github pr review or other external comment workflows. use interactive direct builds when you want the raw compound feel: planning prompts, full review context, and review behavior that can apply safe fixes when the methodology permits it. ## examples interactive first feature build — the default posture. planning asks blocking questions, gates wait for approval, and review may apply safe fixes: ```sh gc bd create \"add rate limiting to the public api\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/rate-limiting/build \\ --var interaction_mode=interactive \\ --var review_mode=interactive \\ --var drain_policy=separate ``` autonomous run that ships — no blocking prompts; decisions are recorded as evidence, and publish pushes the branch and opens a pr after all checks pass: ```sh gc bd create \"migrate config parsing from ini to toml\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/toml-config/build \\ --var interaction_mode=autonomous \\ --var review_mode=agent \\ --var drain_policy=separate \\ --var push=true \\ --var open_pr=true ``` report-only review posture — review lanes produce findings without applying fixes, the adapter shape for feeding an external comment workflow such as github pr review: ```sh gc bd create \"harden webhook signature verification\" gc sling gc.run-operator --on compound-build \\ --var artifact_root=plans/webhook-hardening/build \\ --var interaction_mode=headless \\ --var review_mode=report ``` ## what's vendored - formula: `compound-build` - expansion formulas: `compound-plan-review`, `compound-code-review`, `compound-resolution` - methodology selector formulas: `compound-planning`, `compound-decomposition`, `compound-implementation`, `compound-review`, `compound-fix-loop` - implementation item formulas: `compound-work`, `compound-work-item` - vendored skills: `ce-brainstorm`, `ce-plan`, `ce-work`, `ce-code-review`, and `ce-compound` - vendored agent personas under `vendor/compound-engineering-plugin/agents/`, adapted as the pack-local `ce-*` agents - provenance: `vendor/compound-engineering-plugin/upstream.toml` records the upstream repository, pinned commit, and mit license upstream compound engineering skills use persona subagents. this pack converts those fanouts into gas city item formulas and expansion formulas with explicit `gc.*` lanes. the vendored agent files are prompt inputs only; the workflow must not invoke provider-native subagents, slash commands, task tools, or the upstream plugin runtime. `ce-work` is an override of the inherited `do-work` implementation step; `ce-compound` is used during the `finalize` stage through `compound-resolution`; the base workflow does not add a separate compound stage. ## compatibility ledger the pack-local compatibility ledger lives at [`compound-engineering/requirements.md`](./requirements.md) and records the build-base contract proofs, including the inherited `gc` import, preserved anchor order, mode and drain declarations, persona-lane routing, and the evidence commands that reproduce each claim. 0.1.0 first registry release of the compound engineering build factory. 0.1.1 implementer commits in the source-anchor worktree; code review and compound resolution record validated report paths on the workflow root; apply-findings keeps artifacts schema-valid and the plan is re-validated after plan review; picks up newer gascity pack commits (do-work now needs an origin remote).", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/compound-engineering/README.md", "content": "# Compound Engineering Pack\n\nThis pack runs [Every Inc.'s Compound Engineering](https://github.com/EveryInc/compound-engineering-plugin)\nmethodology as a Gas City build factory: brainstormed requirements, a written\nplan that a persona panel reviews before any code exists, parallel\nimplementation, the widest reviewer fanout of any pack in this repository, and\na compounding step that turns each build's findings into durable learnings.\nYou import one pack, launch one formula (`compound-build`), and get the full\nlifecycle with retries, persistence, and observable per-stage artifacts.\n\n## When to choose compound-engineering\n\n- **Review depth matters most.** The post-implementation review fans out to up\n to 17 specialist lanes (correctness, testing, security, performance, data\n migration, API contracts, and more), versus `build-basic`'s single review\n lane.\n- **You want plans reviewed before code.** A four-persona plan review loop\n (coherence, feasibility, scope, architecture) must approve the plan before\n decomposition starts.\n- **You want learnings to compound.** A learnings-research lane feeds past\n findings into each review, and the finalize stage distills new learnings via\n the vendored `ce-compound` skill.\n- **Pick a different pack when...** you want strict per-task TDD with approval\n gates (`superpowers`), document-first story decomposition (`bmad`),\n founder/PM-flavored QA and release gates (`gstack`), or the fewest moving\n parts (`build-basic` in the base `gascity` pack).\n\n## Quick start\n\nPrerequisites: git, and a repository you want agents to work on.\n\n1. **Install Gas City and start a city** (skip anything you have already\n done):\n\n ```sh\n brew install gascity\n gc init ~/my-city\n cd ~/my-city\n gc start\n ```\n\n2. **Register your repository as a rig.** A *rig* is a repo Gas City agents\n can work in:\n\n ```sh\n cd ~/your-project # any git repo; `git init` a fresh one if needed\n gc rig add .\n ```\n\n3. **Import the pack.** From the city directory, add `compound-engineering`\n at city scope. This writes the import, fetches the latest release, and\n pins it in `packs.lock` — no clone needed. The pack imports the Gas City\n base pack internally as `gc`, so the `build-base` contract and `gc.*`\n formulas come along transitively:\n\n ```sh\n gc import add https://github.com/gastownhall/gascity-packs.git//compound-engineering\n ```\n\n4. **Import the rig roles in `city.toml`.** Each rig that should run work\n also needs the `gascity/roles` import, which provides the worker role\n agents (`gc.run-operator`, `gc.implementation-worker`, and friends) that\n formulas route to; run `gc import install` after editing:\n\n ```toml\n [[rigs]]\n name = \"your-project\"\n\n [rigs.imports.gc]\n source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\"\n ```\n\n Contributors working on the packs themselves can clone\n `https://github.com/gastownhall/gascity-packs` and point either `source`\n at the local path (for example `../gascity-packs/compound-engineering`)\n instead.\n\n5. **Verify the formula is visible** from the rig context:\n\n ```sh\n gc formula catalog --json\n gc formula show compound-build --json\n ```\n\n6. **Launch your first build.** `compound-build` is a targeted formula\n (`target_required = true`), so create a bead describing the goal and sling\n the workflow at it. Launch from the target rig context (or pass your normal\n `--rig ` selection):\n\n ```sh\n gc bd create \"Add a --json flag to the export command\"\n gc sling gc.run-operator --on compound-build \\\n --var artifact_root=plans/json-flag/build \\\n --var drain_policy=separate\n ```\n\n7. **Watch it run.** The build walks `prepare -> requirements -> plan ->\n plan-review -> decompose -> implement -> review -> finalize -> publish`.\n Each stage writes its artifact (requirements, plan, plan-review report,\n per-item implementation summaries, code-review report, final report) under\n `artifact_root` in your rig as it closes, and records the path in\n `gc.build.*` metadata on the workflow root bead. The build is done when the\n finalize stage records `gc.build.final_report_path` and publish completes\n (a no-op unless you set `push`/`open_pr`).\n\n## Stage map\n\n`compound-build` declares `extends = [\"build-base\"]` and overrides stages of\nthe inherited contract under their base ids; no anchor is renamed, skipped, or\nreordered.\n\n| build-base stage | compound-build behavior |\n| ---------------- | ----------------------- |\n| `prepare` | Inherited from `build-base`. |\n| `requirements` | Vendored `ce-brainstorm` skill writes the requirements artifact. |\n| `plan` | Vendored `ce-plan` skill writes the plan artifact. |\n| `plan-review` | Expands `compound-plan-review`: coherence, feasibility, scope-guardian, and architecture-strategist lanes fan out, a synthesizer fans in, `ce-plan` applies findings, and the loop repeats (up to 6 passes) until approved. |\n| `decompose` | Inherited `build-base` task-graph decomposition. |\n| `implement` / `implement-same-session` | Gas City drain. `separate` drains `compound-work` item formulas in parallel with exclusive member access; `same-session` drains `compound-work-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`. Both run the vendored `ce-work` role per item. |\n| `review` | Expands `compound-code-review`: the full reviewer-persona fanout, synthesis, and an apply-fix lane, looping (up to 8 passes) until approved. |\n| `finalize` | Expands `compound-resolution`: `inventory-artifacts -> review-resolution` (`ce-pr-comment-resolver`) `-> synthesize-resolution` (`ce-compound` learnings distillation). |\n| `publish` | Inherited from `build-base`; gated by `push` and `open_pr`. |\n\nSupported postures, declared in `[metadata.gc.methodology]`:\n\n- `interaction_modes`: `interactive`, `autonomous`, `headless`\n- `review_modes`: `report`, `agent`, `interactive`\n- `allowed_drain_policies`: `separate`, `same-session` (implementation\n strategy is `drain`)\n\nFor mode vocabulary, treat `interaction_mode` as the planning and human-gate\naxis, and `review_mode` as the review authority axis. Compound's raw\n`mode:agent` maps to machine handoff/report behavior, while default interactive\nreview maps to a direct build where the reviewer is allowed to own safe fix\napplication.\n\n```mermaid\nflowchart TD\n classDef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827;\n classDef compound fill:#ecfdf5,stroke:#059669,color:#111827;\n classDef infra fill:#fff7ed,stroke:#d97706,color:#111827;\n classDef terminal fill:#f9fafb,stroke:#6b7280,color:#111827;\n\n Start[\"Launch compound-build\"]:::terminal --> Prepare[\"prepare
inherited build-base\"]:::inherited\n Prepare --> Requirements[\"requirements
ce-brainstorm override\"]:::compound\n Requirements --> Plan[\"plan
ce-plan override\"]:::compound\n\n subgraph PlanReview[\"plan-review override: Compound persona review loop\"]\n direction TB\n Plan --> PlanSetup[\"setup plan-review context
Compound override\"]:::compound\n PlanSetup --> PlanFanout[\"fan out plan persona lanes\"]:::infra\n PlanFanout --> Coherence[\"coherence-review\"]:::compound\n PlanFanout --> Feasibility[\"feasibility-review\"]:::compound\n PlanFanout --> Scope[\"scope-review\"]:::compound\n PlanFanout --> Architecture[\"architecture-review\"]:::compound\n Coherence --> PlanSynth[\"synthesize-plan-review\"]:::compound\n Feasibility --> PlanSynth\n Scope --> PlanSynth\n Architecture --> PlanSynth\n PlanSynth --> ApplyPlan[\"apply-plan-findings\"]:::compound\n ApplyPlan --> PlanGate{\"plan approved?
graph check\"}:::infra\n PlanGate -->|no: next review pass| PlanFanout\n PlanGate -->|yes| PlanReviewDone[\"finalize approved plan review\"]:::compound\n end\n\n PlanReviewDone --> Decompose[\"decompose
inherited build-base task graph\"]:::inherited\n Decompose --> DrainChoice{\"drain policy
Gas City drain\"}:::infra\n DrainChoice -->|separate| DrainSeparate[\"separate drain
parallel by convoy deps\"]:::infra\n DrainChoice -->|same-session| DrainShared[\"same-session drain
single worker lane\"]:::infra\n\n subgraph ItemFormula[\"compound-work item formulas: one formula instance per implementation bead\"]\n direction TB\n SeparatePrep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> CompoundWork[\"ce-work implement override\"]:::compound\n SharedWork[\"ce-work shared implement-item override\"]:::compound\n CompoundWork --> CloseItem[\"close source anchor
inherited do-work\"]:::inherited\n end\n\n DrainSeparate --> SeparatePrep\n DrainShared --> SharedWork\n CloseItem --> DrainFanIn[\"fan in
drain waits for all item roots\"]:::infra\n SharedWork --> DrainFanIn\n\n DrainFanIn --> CodeSetup[\"review setup
Compound code-review override\"]:::compound\n\n subgraph CodeReview[\"post-implementation review override: Compound reviewer loop\"]\n direction TB\n CodeSetup --> CodeSelector[\"select conditional reviewer lanes
CE cheap selector\"]:::compound\n CodeSelector --> CodeFanout[\"fan out always-on lanes
and conditional gates\"]:::infra\n\n subgraph CodeLanes[\"parallel reviewer lanes: sibling beads with no needs between them\"]\n direction LR\n Correctness[\"correctness\"]:::compound\n Testing[\"testing\"]:::compound\n Maintainability[\"maintainability\"]:::compound\n Standards[\"project standards\"]:::compound\n AgentNative[\"agent-native\"]:::compound\n Learnings[\"learnings research\"]:::compound\n ConditionalGates[\"cheap gates
close skipped lanes\"]:::infra\n CrossCutting[\"selected cross-cutting
security, performance, API,
data, reliability, adversarial,
previous comments\"]:::compound\n StackSpecific[\"selected stack-specific
frontend races, Swift iOS,
deployment verification\"]:::compound\n GapAnalysis[\"gap analysis\"]:::compound\n end\n\n CodeFanout --> Correctness\n CodeFanout --> Testing\n CodeFanout --> Maintainability\n CodeFanout --> Standards\n CodeFanout --> AgentNative\n CodeFanout --> Learnings\n CodeFanout --> ConditionalGates\n ConditionalGates -->|selected| CrossCutting\n ConditionalGates -->|selected| StackSpecific\n ConditionalGates -->|skipped: no-op artifact| CodeFanIn\n CodeFanout --> GapAnalysis\n Correctness --> CodeFanIn[\"fan in
wait for all review artifacts\"]:::infra\n Testing --> CodeFanIn\n Maintainability --> CodeFanIn\n Standards --> CodeFanIn\n AgentNative --> CodeFanIn\n Learnings --> CodeFanIn\n CrossCutting --> CodeFanIn\n StackSpecific --> CodeFanIn\n GapAnalysis --> CodeFanIn\n CodeFanIn --> CodeSynth[\"synthesize-code-review\"]:::compound\n CodeSynth --> ApplyReview[\"apply-review-findings\"]:::compound\n ApplyReview --> CodeGate{\"implementation approved?
graph check\"}:::infra\n CodeGate -->|no: fix and re-review| CodeFanout\n CodeGate -->|yes| CodeReviewDone[\"finalize approved code review\"]:::compound\n end\n\n CodeReviewDone --> ResolutionStart[\"finalize
compound-resolution override\"]:::compound\n\n subgraph Resolution[\"compound-resolution finalization\"]\n direction TB\n ResolutionStart --> Inventory[\"inventory-artifacts\"]:::compound\n Inventory --> ReviewResolution[\"review-resolution
PR/comment resolver\"]:::compound\n ReviewResolution --> SynthesizeResolution[\"synthesize-resolution
ce-compound\"]:::compound\n SynthesizeResolution --> Finalize[\"finalize build result\"]:::compound\n end\n\n Finalize --> Publish[\"publish
inherited build-base\"]:::inherited\n Publish --> Done[\"workflow complete\"]:::terminal\n```\n\nBlue nodes are inherited Gas City behavior, green nodes are Compound\nEngineering-specific overrides, and amber nodes are Gas City graph, convoy, or\ndrain infrastructure. The plan-review and code-review stages are explicit graph\nloops: required findings route back through the same fanout/fix stage instead\nof falling through to decomposition or finalization. Implementation keeps the\nGas City drain lifecycle, so independent convoy members can run in parallel\nwhile each member receives a Compound `ce-work` item formula.\n\n## Customization\n\nAll knobs are launch vars on `compound-build` (most are inherited from\n`build-base`); pass them as `--var key=value`:\n\n| Variable | Default | What it changes |\n| -------- | ------- | --------------- |\n| `artifact_root` | required | Directory under the rig where every stage artifact is written. |\n| `interaction_mode` | `interactive` | Human participation in planning and gates: `interactive` keeps blocking questions and approval menus, `autonomous` decides and records evidence, `headless` never blocks. |\n| `review_mode` | `agent` | Review authority: `report` writes findings only, `agent` is a structured machine handoff whose caller applies fixes, `interactive` lets the review apply safe fixes directly. |\n| `drain_policy` | `separate` | `separate` runs items in parallel worktrees; `same-session` runs them serially in one shared session. |\n| `implementation_target` | `compound-engineering.ce-work` | Role that implements each drained item and the apply-fix lanes. |\n| `push` | `false` | Allow publish to push after all checks pass. |\n| `open_pr` | `false` | Allow publish to open a PR after all checks pass. |\n| `max_iterations` | `10` | Maximum implementation/review fix attempts. |\n| `planning_formula` | `compound-planning` | Methodology selector adapters use to delegate planning. |\n| `decomposition_formula` | `compound-decomposition` | Methodology selector for task creation. |\n| `implementation_formula` | `compound-implementation` | Methodology selector for the implementation entry. |\n| `implementation_item_formula` | `compound-work-item` | Item formula for shared-drain adapters. |\n| `code_review_formula` | `compound-review` | Methodology selector for standalone code review. |\n| `review_fix_formula` | `compound-fix-loop` | Methodology selector for the standalone review-fix loop. |\n\nThe selector vars are how adapters such as the `build-from-*` continuation\nformulas pick the Compound implementations of each base methodology contract\nwithout overriding any formula. `context_path`, `requirements_path`,\n`plan_path`, and `decomposition_path` (inherited from `build-base`) let you\nreuse artifacts that already exist.\n\n**Prompt customization by asset shadowing.** Every step body is a Markdown\nasset at `assets/workflows//.md`, resolvable through the\nnormal import/layer search path. To inject repository-specific security review\npolicy, place a file at\n`assets/workflows/compound-code-review/{target}.security-review.md` in a\nhigher-priority city or local pack layer (the `{target}.` prefix is literal in\nthe filename); to reshape planning, shadow\n`assets/workflows/compound-build/plan.md`. The shadowing file replaces the\nprompt text without changing the formula graph.\n\n**Review lane selection and extension.** The always-on lanes (correctness,\ntesting, maintainability, project standards, agent-native, learnings research,\ngap analysis) run as sibling beads on every pass. Conditional cross-cutting\nlanes (security, performance, API contract, data migration, reliability,\nadversarial, previous comments) and stack-specific lanes (frontend races,\nSwift iOS, deployment verification) each sit behind a cheap selector gate run\nby `ce-code-review-selector`; skipped gates close their lane with a no-op\nartifact so the synthesis fan-in never blocks. To add a lane, copy the\n`compound-code-review` expansion into a higher-priority formula layer, add the\nlane (gated or not), and include it in the `synthesize-code-review` `needs`\nlist. See \"Stable Workflow Override Interface\" in\n[`gascity/README.md`](../gascity/README.md) for the compatibility rules both\ncustomization modes must follow.\n\nCompound review fanout is the pack's main showcase. The raw `ce-code-review`\npersona roster becomes sibling review beads with selector gates, synthesis, and\nan apply-fix lane. Use report-only adapter runs for GitHub PR review or other\nexternal comment workflows. Use interactive direct builds when you want the\nraw Compound feel: planning prompts, full review context, and review behavior\nthat can apply safe fixes when the methodology permits it.\n\n## Examples\n\nInteractive first feature build — the default posture. Planning asks blocking\nquestions, gates wait for approval, and review may apply safe fixes:\n\n```sh\ngc bd create \"Add rate limiting to the public API\"\ngc sling gc.run-operator --on compound-build \\\n --var artifact_root=plans/rate-limiting/build \\\n --var interaction_mode=interactive \\\n --var review_mode=interactive \\\n --var drain_policy=separate\n```\n\nAutonomous run that ships — no blocking prompts; decisions are recorded as\nevidence, and publish pushes the branch and opens a PR after all checks pass:\n\n```sh\ngc bd create \"Migrate config parsing from INI to TOML\"\ngc sling gc.run-operator --on compound-build \\\n --var artifact_root=plans/toml-config/build \\\n --var interaction_mode=autonomous \\\n --var review_mode=agent \\\n --var drain_policy=separate \\\n --var push=true \\\n --var open_pr=true\n```\n\nReport-only review posture — review lanes produce findings without applying\nfixes, the adapter shape for feeding an external comment workflow such as\nGitHub PR review:\n\n```sh\ngc bd create \"Harden webhook signature verification\"\ngc sling gc.run-operator --on compound-build \\\n --var artifact_root=plans/webhook-hardening/build \\\n --var interaction_mode=headless \\\n --var review_mode=report\n```\n\n## What's vendored\n\n- Formula: `compound-build`\n- Expansion formulas: `compound-plan-review`, `compound-code-review`,\n `compound-resolution`\n- Methodology selector formulas: `compound-planning`,\n `compound-decomposition`, `compound-implementation`, `compound-review`,\n `compound-fix-loop`\n- Implementation item formulas: `compound-work`, `compound-work-item`\n- Vendored skills: `ce-brainstorm`, `ce-plan`, `ce-work`, `ce-code-review`,\n and `ce-compound`\n- Vendored agent personas under `vendor/compound-engineering-plugin/agents/`,\n adapted as the pack-local `ce-*` agents\n- Provenance: `vendor/compound-engineering-plugin/upstream.toml` records the\n upstream repository, pinned commit, and MIT license\n\nUpstream Compound Engineering skills use persona subagents. This pack converts\nthose fanouts into Gas City item formulas and expansion formulas with explicit\n`gc.*` lanes. The vendored agent files are prompt inputs only; the workflow must\nnot invoke provider-native subagents, slash commands, task tools, or the\nupstream plugin runtime. `ce-work` is an override of the inherited `do-work`\nimplementation step; `ce-compound` is used during the `finalize` stage through\n`compound-resolution`; the base workflow does not add a separate compound\nstage.\n\n## Compatibility ledger\n\nThe pack-local compatibility ledger lives at\n[`compound-engineering/REQUIREMENTS.md`](./REQUIREMENTS.md) and records the\nbuild-base contract proofs, including the inherited `gc` import, preserved\nanchor order, mode and drain declarations, persona-lane routing, and the\nevidence commands that reproduce each claim." }, "og_image": "/og/compound-engineering.svg", - "latest": "0.1.0", + "latest": "0.1.1", "releases": [ { "version": "0.1.0", @@ -95,48 +103,40 @@ "hash": "sha256:7981abfa28021a99accacc097d32a04e2d28c344f09ebf17e3f0ca51768140af", "description": "First registry release of the Compound Engineering build factory.", "withdrawn": false + }, + { + "version": "0.1.1", + "ref": "main", + "commit": "390cac1a8a0478b6dfde603dae00a43ed9748d13", + "hash": "sha256:12bf7fbd2ab86d0fc2a866ac7562bc957908eae408c779e539af3c3d14ede4b9", + "description": "Implementer commits in the source-anchor worktree; code review and compound resolution record validated report paths on the workflow root; apply-findings keeps artifacts schema-valid and the plan is re-validated after plan review; picks up newer gascity pack commits (do-work now needs an origin remote).", + "withdrawn": false } ] }, { - "pack_key": "gascity-packs--contributing", + "pack_key": "gascity-packs--contributing-to-gascity", "registry": "gascity-packs", - "name": "contributing", + "name": "contributing-to-gascity", "tier": "maintained", "publisher": "Gas City", "description": "The external-contributor lifecycle for gastownhall/gascity — write a good issue, find priority work, plan a PR, map its blast radius, and self-review against the codebase audit. Each step produces an artifact you act on; the pack stops before pushing — opening the PR is your call. Self-contained: gas-city's actual standards (the B-rule adoption-review audit, blast-radius dimensions, test tiers) are baked into the skills; no imports.", - "source": "https://github.com/gastownhall/gascity-packs/tree/main/contributing", + "source": "https://github.com/gastownhall/gascity-packs/tree/main/contributing-to-gascity", "source_kind": "git", - "search_text": "contributing gascity-packs maintained gas city the external-contributor lifecycle for gastownhall/gascity — write a good issue, find priority work, plan a pr, map its blast radius, and self-review against the codebase audit. each step produces an artifact you act on; the pack stops before pushing — opening the pr is your call. self-contained: gas-city's actual standards (the b-rule adoption-review audit, blast-radius dimensions, test tiers) are baked into the skills; no imports. https://github.com/gastownhall/gascity-packs/tree/main/contributing git # contributing the external-contributor lifecycle for [gastownhall/gascity](https://github.com/gastownhall/gascity), distributed as a gas city pack. it gives an outside contributor the full journey of landing work upstream — and carries gas city's **actual** standards at every step. this is not generic pr discipline: the adoption-review audit (b1–b36), the blast-radius dimensions, the design-capture rule, the five test tiers, and the code conventions a gas city maintainer reviews against are all **baked into the skill text**, so your own coding agent applies them by reading the skill. ## self-contained by design this pack has **no imports** and depends on nothing you might not have — no internal agents, no sibling pack, no maintainer-only tooling. every skill is a self-applicable checklist. you run the workflow with `git`, `gh`, and your coding agent reading the skills; that's it. > the generic [pr-pipeline] pack is still the right choice for a city that wants > contributor discipline without gas city's particular standards. this pack is > the gas-city-specific instantiation — it trades portability for baked-in > standards. ## the lifecycle it starts at a **router** — gate 0, *\"a priority issue, or your own?\"* — that feeds two step-1 variants (do **1a or 1b** depending on what you're starting from); both converge on step 2. | step | what you do | skill | |------|-------------|-------| | 0 | **start here.** route on entry: priority issue → 1a; your own issue/bug → 1b | [`start-contribution`](skills/start-contribution/skill.md) | | 1a | find a priority issue to work on (someone else's, or a triage pick) | [`find-work`](skills/find-work/skill.md) | | 1b | write a high-quality issue (something you found) | [`write-issue`](skills/write-issue/skill.md) | | 2 | plan the implementation (incl. blast-radius mapping as phase 2) | [`plan-implementation`](skills/plan-implementation/skill.md) | | 3 | fine-tune the diff (incl. the review as its final gate) | [`fine-tune`](skills/fine-tune/skill.md) | two sub-skills are **a phase of a parent skill and a standalone utility** — not pipeline steps of their own. run each as part of its parent, or directly: - [`map-blast-radius`](skills/map-blast-radius/skill.md) — phase 2 of `plan-implementation`; standalone for ad-hoc impact mapping (scoping a refactor, mapping a change for someone else). - [`review`](skills/review/skill.md) — the final gate of `fine-tune`; standalone to review any diff/branch against the gas city standard (incl. before a pr exists, or as a maintainer on an incoming contribution). step 1 has two mutually-exclusive variants — the router (step 0) picks one, then both lead to step 2: - **implement/fix a priority issue** — step 1a (`find-work` to triage), then plan it (step 2). - **implement/fix your own issue** — step 1b (`write-issue`), then plan it (step 2). the [`start-contribution`](skills/start-contribution/skill.md) skill is the operational map and the entry router: it runs gate 0, branches to 1a or 1b, and links each step to its skill. ## three ways to run it the same lifecycle, the same standards, three delivery modes: - **skills (agent-read)** — your coding agent applies a step by *reading* the skill text. this is the default for a contributor working with a coding agent: no orchestration, no city required. start at [`start-contribution`](skills/start-contribution/skill.md). - **formulas (gc-orchestrated)** — thin `mol-contributing-*` wrappers let a city *dispatch* a single step to a transient worker session as a gc formula. each wrapper is orchestration only: it resolves the run's root bead, records state, writes the output artifact, and enforces blocking early-exits — then **delegates every standard to its sibling skill**. the skill stays the single source of truth; the formula never restates the audit, the gates, or the dimensions. - **mayor-orchestrated lifecycle (whole-process)** — the [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) skill drives the *whole* lifecycle as a **mayor loop**: it dispatches the per-step formulas in order and **pauses at each of the four human gates** (see [the gating model](#the-gating-model)). it owns only dispatch + gate; every standard stays in the skills, every step's mechanics stay in the formulas. no formula is modified — the loop consumes the root-bead notes the formulas already emit. | formula | applies skill | models on (pr-pipeline) | | -------------------------------------- | -------------------------------------------------------------- | ----------------------- | | `mol-contributing-find-work` | [`find-work`](skills/find-work/skill.md) | `mol-pr-triage` | | `mol-contributing-plan-implementation` | [`plan-implementation`](skills/plan-implementation/skill.md) | `mol-pr-start` | | `mol-contributing-map-blast-radius` | [`map-blast-radius`](skills/map-blast-radius/skill.md) | `mol-pr-blast-radius` | | `mol-contributing-review` | [`review`](skills/review/skill.md) | `mol-pr-review` | | `mol-contributing-fine-tune` | [`fine-tune`](skills/fine-tune/skill.md) | `mol-pr-ship` | there is no formula for `write-issue`: issue authoring sits upstream of the pr flow (you have nothing to orchestrate against yet), so use that skill directly. formula outputs land under `.gc/contributing/` (work-queue, plan, blast-radius, review, and fine-tune reports), and run state is recorded in the molecule's root-bead notes. like the skills, the formulas stop before pushing — `mol-contributing-fine-tune` ends at the readiness report and never runs `git push` or `gh pr create`. ## the gating model the lifecycle has **four human gates** — pick an issue (gate 1), confirm the plan before any code (gate 2), review the readiness report (gate 3), and the entry branch itself (gate 0). those gates are **real**, but there is deliberately **no single end-to-end formula** that runs the whole lifecycle unattended. here's why. a `mol-contributing-*` formula runs in an **unattended transient worker session** — nobody is at the keyboard. a formula gate can only *auto-proceed* or *halt-and-stop*; it cannot interactively pause and resume the same run. but the contributor gates are interactive human decisions, and one step (implementation) is done by a human by hand. so the gates are realized at **formula boundaries / the mayor layer**, not inside a worker: - in **per-step formula** mode, each formula ends by writing its artifact and printing `next: dispatch mol-contributing-`. that handoff line **is** a gate — a city operator reads the artifact and decides whether to dispatch the next step. - in **mayor-orchestrated** mode, [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) formalizes the same boundary into a loop: dispatch a step → read the root-bead notes the worker wrote → surface the artifact + recommended action to the human → **wait for the decision** → dispatch the next step. the pause works because the mayor is the attended agent actually talking to the human. across long waits it uses `gc handoff`, and a fresh mayor resumes from the bead notes. the mayor loop never auto-pushes, never opens a pr, and never auto-implements — gate 2, gate 3, and the final stop are preserved. ## why two map skills two skills describe the same lifecycle, one per audience: - [`start-contribution`](skills/start-contribution/skill.md) — the **contributor entry**. a coding agent reads each step's skill and **implements by hand**. no city, no `gc`. - [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) — the **mayor umbrella**. a city operator **dispatches** the steps to transient workers and gates each one. they are split because a single skill would carry one `description` for two very different trigger contexts and two registers — \"apply by hand\" vs. \"dispatch + gate\" — and would mis-route both. keeping them separate makes each `description` precise. `orchestrate-contribution` **reuses `start-contribution`'s gate 0 branch logic rather than restating it**, so the entry skill stays the single source of truth for the map. ## nothing here pushes for you each step produces an artifact you act on — an issue body, a plan, a blast-radius report, an audit verdict, a readiness report. the `fine-tune` skill stops at the readiness report. **pushing the branch and opening the pr are your call.** ## usage in your city's `pack.toml`: ```toml [imports.contributing] source = \"../packs/contributing\" # path; or git url when published ``` then the skills load for your coding agent. you can also read them directly from this directory — they're self-contained markdown. ## migrating from the previous version the current pack is **0.4.0**. if you adopted an earlier version, the skill and formula **names** changed during the lifecycle rework — nothing else about how a step behaves did. every standard baked into the skills (the b1–b36 adoption audit, the blast-radius dimensions, the five test tiers, the gating model) is unchanged, outputs still land under `.gc/contributing/`, and nothing here pushes a branch or opens a pr. **the only break is the names**, so update any reference that pins one: | old name | new name | |----------|----------| | `contributing` (entry skill) | [`start-contribution`](skills/start-contribution/skill.md) | | `plan-pr` | [`plan-implementation`](skills/plan-implementation/skill.md) | | `blast-radius` | [`map-blast-radius`](skills/map-blast-radius/skill.md) | | `ship` | [`fine-tune`](skills/fine-tune/skill.md) | | `check` | [`review`](skills/review/skill.md) | | `mol-contributing-triage` | `mol-contributing-find-work` | | `mol-contributing-start` | `mol-contributing-plan-implementation` | | `mol-contributing-blast-radius` | `mol-contributing-map-blast-radius` | | `mol-contributing-ship` | `mol-contributing-fine-tune` | (`mol-contributing-review` keeps its name.) to upgrade: - **skills (agent-read):** enter at [`start-contribution`](skills/start-contribution/skill.md) instead of `contributing`; the renamed steps link from there. - **formulas (gc-orchestrated):** update any `gc sling mol-contributing-*` call that uses an old formula name per the table above. - **new in 0.4.0:** the [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) mayor-mode umbrella, which drives the whole lifecycle by dispatching the per-step formulas and gating each one. it's additive — existing per-step and agent-read usage keeps working unchanged. ## pack contents ``` contributing/ ├── pack.toml schema=2; no imports (self-contained) ├── readme.md ├── skills/ agent-read + mayor-mode maps │ ├── start-contribution/skill.md entry router (gate 0); the lifecycle map │ ├── orchestrate-contribution/skill.md mayor loop: dispatch the steps, gate each │ ├── write-issue/skill.md file a maintainer-grade issue │ ├── find-work/skill.md triage open issues into a work-queue │ ├── plan-implementation/skill.md adoption-review-aware implementation planning │ ├── map-blast-radius/skill.md map the impact surface of a change │ ├── fine-tune/skill.md pre-push fine-tuning loop (ends with review) │ └── review/skill.md mechanical gates + the b1–b36 audit ├── formulas/ gc-orchestrated mode (thin wrappers over the skills) │ ├── mol-contributing-find-work.formula.toml -> find-work │ ├── mol-contributing-plan-implementation.formula.toml -> plan-implementation │ ├── mol-contributing-map-blast-radius.formula.toml -> map-blast-radius │ ├── mol-contributing-review.formula.toml -> review │ └── mol-contributing-fine-tune.formula.toml -> fine-tune ├── doctor/ preflight checks (gc, gh, git present) │ ├── check-gc.sh + gc/doctor.toml │ ├── check-gh.sh + gh/doctor.toml │ └── check-git.sh + git/doctor.toml └── tests/ ├── test_contributing_skill_frontmatter.py skills have name + description ├── test_contributing_pack_structure.py self-contained; doctor scripts executable └── test_contributing_formulas.py formulas parse; each references a real skill ``` ## tests ```sh python3 -m pytest contributing/tests/ ``` [pr-pipeline]: https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline 0.1.0 initial contributing pack release. 0.2.0 self-contained gas-city contributor lifecycle: drops the pr-pipeline import, bakes in the b-rule adoption-review audit, blast-radius dimensions, and test-tier strategy (7 skills, zero imports). 0.2.1 yaml-safe skill front matter (0.2.0 had an unquoted colon that broke parsing) plus a durability pass — removes time-pinned references (pr-number citations, dated product names, build-timeout numbers, drift-prone counts) so the skills resist staleness. no behavior change.", + "search_text": "contributing-to-gascity gascity-packs maintained gas city the external-contributor lifecycle for gastownhall/gascity — write a good issue, find priority work, plan a pr, map its blast radius, and self-review against the codebase audit. each step produces an artifact you act on; the pack stops before pushing — opening the pr is your call. self-contained: gas-city's actual standards (the b-rule adoption-review audit, blast-radius dimensions, test tiers) are baked into the skills; no imports. https://github.com/gastownhall/gascity-packs/tree/main/contributing-to-gascity git # contributing the external-contributor lifecycle for [gastownhall/gascity](https://github.com/gastownhall/gascity), distributed as a gas city pack. it gives an outside contributor the full journey of landing work upstream — and carries gas city's **actual** standards at every step. this is not generic pr discipline: the adoption-review audit (b1–b36), the blast-radius dimensions, the design-capture rule, the five test tiers, and the code conventions a gas city maintainer reviews against are all **baked into the skill text**, so your own coding agent applies them by reading the skill. ## self-contained by design this pack has **no imports** and depends on nothing you might not have — no internal agents, no sibling pack, no maintainer-only tooling. every skill is a self-applicable checklist. you run the workflow with `git`, `gh`, and your coding agent reading the skills; that's it. > the generic [pr-pipeline] pack is still the right choice for a city that wants > contributor discipline without gas city's particular standards. this pack is > the gas-city-specific instantiation — it trades portability for baked-in > standards. ## the lifecycle it starts at a **router** — gate 0, *\"a priority issue, or your own?\"* — that feeds two step-1 variants (do **1a or 1b** depending on what you're starting from); both converge on step 2. | step | what you do | skill | |------|-------------|-------| | 0 | **start here.** route on entry: priority issue → 1a; your own issue/bug → 1b | [`start-contribution`](skills/start-contribution/skill.md) | | 1a | find a priority issue to work on (someone else's, or a triage pick) | [`find-work`](skills/find-work/skill.md) | | 1b | write a high-quality issue (something you found) | [`write-issue`](skills/write-issue/skill.md) | | 2 | plan the implementation (incl. blast-radius mapping as phase 2) | [`plan-implementation`](skills/plan-implementation/skill.md) | | 3 | fine-tune the diff (incl. the review as its final gate) | [`fine-tune`](skills/fine-tune/skill.md) | two sub-skills are **a phase of a parent skill and a standalone utility** — not pipeline steps of their own. run each as part of its parent, or directly: - [`map-blast-radius`](skills/map-blast-radius/skill.md) — phase 2 of `plan-implementation`; standalone for ad-hoc impact mapping (scoping a refactor, mapping a change for someone else). - [`review`](skills/review/skill.md) — the final gate of `fine-tune`; standalone to review any diff/branch against the gas city standard (incl. before a pr exists, or as a maintainer on an incoming contribution). step 1 has two mutually-exclusive variants — the router (step 0) picks one, then both lead to step 2: - **implement/fix a priority issue** — step 1a (`find-work` to triage), then plan it (step 2). - **implement/fix your own issue** — step 1b (`write-issue`), then plan it (step 2). the [`start-contribution`](skills/start-contribution/skill.md) skill is the operational map and the entry router: it runs gate 0, branches to 1a or 1b, and links each step to its skill. ## three ways to run it the same lifecycle, the same standards, three delivery modes: - **skills (agent-read)** — your coding agent applies a step by *reading* the skill text. this is the default for a contributor working with a coding agent: no orchestration, no city required. start at [`start-contribution`](skills/start-contribution/skill.md). - **formulas (gc-orchestrated)** — thin `mol-contributing-*` wrappers let a city *dispatch* a single step to a transient worker session as a gc formula. each wrapper is orchestration only: it resolves the run's root bead, records state, writes the output artifact, and enforces blocking early-exits — then **delegates every standard to its sibling skill**. the skill stays the single source of truth; the formula never restates the audit, the gates, or the dimensions. - **mayor-orchestrated lifecycle (whole-process)** — the [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) skill drives the *whole* lifecycle as a **mayor loop**: it dispatches the per-step formulas in order and **pauses at each of the four human gates** (see [the gating model](#the-gating-model)). it owns only dispatch + gate; every standard stays in the skills, every step's mechanics stay in the formulas. no formula is modified — the loop consumes the root-bead notes the formulas already emit. | formula | applies skill | models on (pr-pipeline) | | -------------------------------------- | -------------------------------------------------------------- | ----------------------- | | `mol-contributing-find-work` | [`find-work`](skills/find-work/skill.md) | `mol-pr-triage` | | `mol-contributing-plan-implementation` | [`plan-implementation`](skills/plan-implementation/skill.md) | `mol-pr-start` | | `mol-contributing-map-blast-radius` | [`map-blast-radius`](skills/map-blast-radius/skill.md) | `mol-pr-blast-radius` | | `mol-contributing-review` | [`review`](skills/review/skill.md) | `mol-pr-review` | | `mol-contributing-fine-tune` | [`fine-tune`](skills/fine-tune/skill.md) | `mol-pr-ship` | there is no formula for `write-issue`: issue authoring sits upstream of the pr flow (you have nothing to orchestrate against yet), so use that skill directly. formula outputs land under `.gc/contributing/` (work-queue, plan, blast-radius, review, and fine-tune reports), and run state is recorded in the molecule's root-bead notes. like the skills, the formulas stop before pushing — `mol-contributing-fine-tune` ends at the readiness report and never runs `git push` or `gh pr create`. ## the gating model the lifecycle has **four human gates** — pick an issue (gate 1), confirm the plan before any code (gate 2), review the readiness report (gate 3), and the entry branch itself (gate 0). those gates are **real**, but there is deliberately **no single end-to-end formula** that runs the whole lifecycle unattended. here's why. a `mol-contributing-*` formula runs in an **unattended transient worker session** — nobody is at the keyboard. a formula gate can only *auto-proceed* or *halt-and-stop*; it cannot interactively pause and resume the same run. but the contributor gates are interactive human decisions, and one step (implementation) is done by a human by hand. so the gates are realized at **formula boundaries / the mayor layer**, not inside a worker: - in **per-step formula** mode, each formula ends by writing its artifact and printing `next: dispatch mol-contributing-`. that handoff line **is** a gate — a city operator reads the artifact and decides whether to dispatch the next step. - in **mayor-orchestrated** mode, [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) formalizes the same boundary into a loop: dispatch a step → read the root-bead notes the worker wrote → surface the artifact + recommended action to the human → **wait for the decision** → dispatch the next step. the pause works because the mayor is the attended agent actually talking to the human. across long waits it uses `gc handoff`, and a fresh mayor resumes from the bead notes. the mayor loop never auto-pushes, never opens a pr, and never auto-implements — gate 2, gate 3, and the final stop are preserved. ## why two map skills two skills describe the same lifecycle, one per audience: - [`start-contribution`](skills/start-contribution/skill.md) — the **contributor entry**. a coding agent reads each step's skill and **implements by hand**. no city, no `gc`. - [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) — the **mayor umbrella**. a city operator **dispatches** the steps to transient workers and gates each one. they are split because a single skill would carry one `description` for two very different trigger contexts and two registers — \"apply by hand\" vs. \"dispatch + gate\" — and would mis-route both. keeping them separate makes each `description` precise. `orchestrate-contribution` **reuses `start-contribution`'s gate 0 branch logic rather than restating it**, so the entry skill stays the single source of truth for the map. ## nothing here pushes for you each step produces an artifact you act on — an issue body, a plan, a blast-radius report, an audit verdict, a readiness report. the `fine-tune` skill stops at the readiness report. **pushing the branch and opening the pr are your call.** ## usage import it into your city from this repository: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//contributing-to-gascity ``` that writes the import and pins it in `packs.lock`. without `--version`, `gc` resolves the newest repository tag, so this form needs a tag that contains the `contributing-to-gascity/` directory. until one exists, pin a commit on `main` with `--version sha:`. the same import by hand in `pack.toml`, followed by `gc import install`: ```toml [imports.contributing-to-gascity] source = \"https://github.com/gastownhall/gascity-packs.git//contributing-to-gascity\" ``` the same tag rule applies here. until a tag contains the directory, add `version = \"sha:\"` to the table. to work on the pack from a local checkout, point `source` at the path instead: ```toml [imports.contributing-to-gascity] source = \"../gascity-packs/contributing-to-gascity\" ``` then the skills load for your coding agent. you can also read them directly from this directory — they're self-contained markdown. ## migrating from the previous version the current pack is **0.5.0**. **0.5.0 is a breaking rename**: the pack moved from `contributing` to `contributing-to-gascity` (directory and import name). update `[imports.contributing]` to `[imports.contributing-to-gascity]` and the `source` path accordingly. the skill and formula names are unchanged. if you adopted an earlier version, the skill and formula **names** changed during the lifecycle rework — nothing else about how a step behaves did. every standard baked into the skills (the b1–b36 adoption audit, the blast-radius dimensions, the five test tiers, the gating model) is unchanged, outputs still land under `.gc/contributing/`, and nothing here pushes a branch or opens a pr. **the only break is the names**, so update any reference that pins one: | old name | new name | |----------|----------| | `contributing` (entry skill) | [`start-contribution`](skills/start-contribution/skill.md) | | `plan-pr` | [`plan-implementation`](skills/plan-implementation/skill.md) | | `blast-radius` | [`map-blast-radius`](skills/map-blast-radius/skill.md) | | `ship` | [`fine-tune`](skills/fine-tune/skill.md) | | `check` | [`review`](skills/review/skill.md) | | `mol-contributing-triage` | `mol-contributing-find-work` | | `mol-contributing-start` | `mol-contributing-plan-implementation` | | `mol-contributing-blast-radius` | `mol-contributing-map-blast-radius` | | `mol-contributing-ship` | `mol-contributing-fine-tune` | (`mol-contributing-review` keeps its name.) to upgrade: - **skills (agent-read):** enter at [`start-contribution`](skills/start-contribution/skill.md) instead of `contributing`; the renamed steps link from there. - **formulas (gc-orchestrated):** update any `gc sling mol-contributing-*` call that uses an old formula name per the table above. - **new in 0.4.0:** the [`orchestrate-contribution`](skills/orchestrate-contribution/skill.md) mayor-mode umbrella, which drives the whole lifecycle by dispatching the per-step formulas and gating each one. it's additive — existing per-step and agent-read usage keeps working unchanged. ## pack contents ``` contributing-to-gascity/ ├── pack.toml schema=2; no imports (self-contained) ├── readme.md ├── skills/ agent-read + mayor-mode maps │ ├── start-contribution/skill.md entry router (gate 0); the lifecycle map │ ├── orchestrate-contribution/skill.md mayor loop: dispatch the steps, gate each │ ├── write-issue/skill.md file a maintainer-grade issue │ ├── find-work/skill.md triage open issues into a work-queue │ ├── plan-implementation/skill.md adoption-review-aware implementation planning │ ├── map-blast-radius/skill.md map the impact surface of a change │ ├── fine-tune/skill.md pre-push fine-tuning loop (ends with review) │ └── review/skill.md mechanical gates + the b1–b36 audit ├── formulas/ gc-orchestrated mode (thin wrappers over the skills) │ ├── mol-contributing-find-work.formula.toml -> find-work │ ├── mol-contributing-plan-implementation.formula.toml -> plan-implementation │ ├── mol-contributing-map-blast-radius.formula.toml -> map-blast-radius │ ├── mol-contributing-review.formula.toml -> review │ └── mol-contributing-fine-tune.formula.toml -> fine-tune ├── doctor/ preflight checks (gc, gh, git present) │ ├── check-gc.sh + gc/doctor.toml │ ├── check-gh.sh + gh/doctor.toml │ └── check-git.sh + git/doctor.toml └── tests/ ├── test_contributing_skill_frontmatter.py skills have name + description ├── test_contributing_pack_structure.py self-contained; doctor scripts executable └── test_contributing_formulas.py formulas parse; each references a real skill ``` ## tests ```sh python3 -m pytest contributing-to-gascity/tests/ ``` [pr-pipeline]: https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline 0.5.0 first release under the new name; the pack was renamed from contributing. carries everything since contributing 0.2.1: the mol-contributing-* formula set, renamed lifecycle skills with an orchestrated umbrella, default-branch resolution from the local ref, and bead commands routed through gc. imports must switch from //contributing to //contributing-to-gascity.", "readme": { - "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/contributing/README.md", - "content": "# Contributing\n\nThe external-contributor lifecycle for\n[gastownhall/gascity](https://github.com/gastownhall/gascity), distributed as a\nGas City pack.\n\nIt gives an outside contributor the full journey of landing work upstream — and\ncarries Gas City's **actual** standards at every step. This is not generic PR\ndiscipline: the adoption-review audit (B1–B36), the blast-radius dimensions, the\ndesign-capture rule, the five test tiers, and the code conventions a Gas City\nmaintainer reviews against are all **baked into the skill text**, so your own\ncoding agent applies them by reading the skill.\n\n## Self-contained by design\n\nThis pack has **no imports** and depends on nothing you might not have — no\ninternal agents, no sibling pack, no maintainer-only tooling. Every skill is a\nself-applicable checklist. You run the workflow with `git`, `gh`, and your\ncoding agent reading the skills; that's it.\n\n> The generic [pr-pipeline] pack is still the right choice for a city that wants\n> contributor discipline without Gas City's particular standards. This pack is\n> the gas-city-specific instantiation — it trades portability for baked-in\n> standards.\n\n## The lifecycle\n\nIt starts at a **router** — GATE 0, *\"a priority issue, or your own?\"* — that\nfeeds two step-1 variants (do **1a or 1b** depending on what you're starting\nfrom); both converge on step 2.\n\n| Step | What you do | Skill |\n|------|-------------|-------|\n| 0 | **Start here.** Route on entry: priority issue → 1a; your own issue/bug → 1b | [`start-contribution`](skills/start-contribution/SKILL.md) |\n| 1a | Find a priority issue to work on (someone else's, or a triage pick) | [`find-work`](skills/find-work/SKILL.md) |\n| 1b | Write a high-quality issue (something you found) | [`write-issue`](skills/write-issue/SKILL.md) |\n| 2 | Plan the implementation (incl. blast-radius mapping as Phase 2) | [`plan-implementation`](skills/plan-implementation/SKILL.md) |\n| 3 | Fine-tune the diff (incl. the review as its final gate) | [`fine-tune`](skills/fine-tune/SKILL.md) |\n\nTwo sub-skills are **a phase of a parent skill AND a standalone utility** — not\npipeline steps of their own. Run each as part of its parent, or directly:\n\n- [`map-blast-radius`](skills/map-blast-radius/SKILL.md) — Phase 2 of\n `plan-implementation`; standalone for ad-hoc impact mapping (scoping a refactor,\n mapping a change for someone else).\n- [`review`](skills/review/SKILL.md) — the final gate of `fine-tune`; standalone\n to review any diff/branch against the Gas City standard (incl. before a PR\n exists, or as a maintainer on an incoming contribution).\n\nStep 1 has two mutually-exclusive variants — the router (step 0) picks one, then\nboth lead to step 2:\n\n- **Implement/fix a priority issue** — step 1a (`find-work` to triage), then plan it (step 2).\n- **Implement/fix your own issue** — step 1b (`write-issue`), then plan it (step 2).\n\nThe [`start-contribution`](skills/start-contribution/SKILL.md) skill is the\noperational map and the entry router: it runs GATE 0, branches to 1a or 1b, and\nlinks each step to its skill.\n\n## Three ways to run it\n\nThe same lifecycle, the same standards, three delivery modes:\n\n- **Skills (agent-read)** — your coding agent applies a step by *reading* the\n skill text. This is the default for a contributor working with a coding agent:\n no orchestration, no city required. Start at\n [`start-contribution`](skills/start-contribution/SKILL.md).\n- **Formulas (gc-orchestrated)** — thin `mol-contributing-*` wrappers let a city\n *dispatch* a single step to a transient worker session as a gc formula. Each\n wrapper is orchestration only: it resolves the run's root bead, records state,\n writes the output artifact, and enforces blocking early-exits — then **delegates\n every standard to its sibling skill**. The skill stays the single source of\n truth; the formula never restates the audit, the gates, or the dimensions.\n- **Mayor-orchestrated lifecycle (whole-process)** — the\n [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md) skill\n drives the *whole* lifecycle as a **mayor loop**: it dispatches the per-step\n formulas in order and **pauses at each of the four human gates** (see [The\n gating model](#the-gating-model)). It owns only dispatch + gate; every standard\n stays in the skills, every step's mechanics stay in the formulas. No formula is\n modified — the loop consumes the root-bead notes the formulas already emit.\n\n| Formula | Applies skill | Models on (pr-pipeline) |\n| -------------------------------------- | -------------------------------------------------------------- | ----------------------- |\n| `mol-contributing-find-work` | [`find-work`](skills/find-work/SKILL.md) | `mol-pr-triage` |\n| `mol-contributing-plan-implementation` | [`plan-implementation`](skills/plan-implementation/SKILL.md) | `mol-pr-start` |\n| `mol-contributing-map-blast-radius` | [`map-blast-radius`](skills/map-blast-radius/SKILL.md) | `mol-pr-blast-radius` |\n| `mol-contributing-review` | [`review`](skills/review/SKILL.md) | `mol-pr-review` |\n| `mol-contributing-fine-tune` | [`fine-tune`](skills/fine-tune/SKILL.md) | `mol-pr-ship` |\n\nThere is no formula for `write-issue`: issue authoring sits upstream of the PR\nflow (you have nothing to orchestrate against yet), so use that skill directly.\n\nFormula outputs land under `.gc/contributing/` (work-queue, plan, blast-radius,\nreview, and fine-tune reports), and run state is recorded in the molecule's\nroot-bead notes. Like the skills, the formulas stop before pushing —\n`mol-contributing-fine-tune` ends at the readiness report and never runs\n`git push` or `gh pr create`.\n\n## The gating model\n\nThe lifecycle has **four human gates** — pick an issue (GATE 1), confirm the plan\nbefore any code (GATE 2), review the readiness report (GATE 3), and the entry\nbranch itself (GATE 0). Those gates are **real**, but there is deliberately **no\nsingle end-to-end formula** that runs the whole lifecycle unattended. Here's why.\n\nA `mol-contributing-*` formula runs in an **unattended transient worker session** —\nnobody is at the keyboard. A formula gate can only *auto-proceed* or\n*halt-and-stop*; it cannot interactively pause and resume the same run. But the\ncontributor gates are interactive human decisions, and one step (implementation)\nis done by a human by hand. So the gates are realized at **formula boundaries / the\nmayor layer**, not inside a worker:\n\n- In **per-step formula** mode, each formula ends by writing its artifact and\n printing `Next: dispatch mol-contributing-`. That handoff line **is** a gate —\n a city operator reads the artifact and decides whether to dispatch the next step.\n- In **mayor-orchestrated** mode, [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md)\n formalizes the same boundary into a loop: dispatch a step → read the root-bead\n notes the worker wrote → surface the artifact + recommended action to the human →\n **wait for the decision** → dispatch the next step. The pause works because the\n mayor is the attended agent actually talking to the human. Across long waits it\n uses `gc handoff`, and a fresh mayor resumes from the bead notes.\n\nThe mayor loop never auto-pushes, never opens a PR, and never auto-implements —\nGATE 2, GATE 3, and the final stop are preserved.\n\n## Why two map skills\n\nTwo skills describe the same lifecycle, one per audience:\n\n- [`start-contribution`](skills/start-contribution/SKILL.md) — the **contributor\n entry**. A coding agent reads each step's skill and **implements by hand**. No\n city, no `gc`.\n- [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md) — the\n **mayor umbrella**. A city operator **dispatches** the steps to transient workers\n and gates each one.\n\nThey are split because a single skill would carry one `description` for two very\ndifferent trigger contexts and two registers — \"apply by hand\" vs. \"dispatch +\ngate\" — and would mis-route both. Keeping them separate makes each `description`\nprecise. `orchestrate-contribution` **reuses `start-contribution`'s GATE 0 branch\nlogic rather than restating it**, so the entry skill stays the single source of\ntruth for the map.\n\n## Nothing here pushes for you\n\nEach step produces an artifact you act on — an issue body, a plan, a blast-radius\nreport, an audit verdict, a readiness report. The `fine-tune` skill stops at the\nreadiness report. **Pushing the branch and opening the PR are your call.**\n\n## Usage\n\nIn your city's `pack.toml`:\n\n```toml\n[imports.contributing]\nsource = \"../packs/contributing\" # path; or git URL when published\n```\n\nThen the skills load for your coding agent. You can also read them directly from\nthis directory — they're self-contained Markdown.\n\n## Migrating from the previous version\n\nThe current pack is **0.4.0**. If you adopted an earlier version, the skill and\nformula **names** changed during the lifecycle rework — nothing else about how a\nstep behaves did. Every standard baked into the skills (the B1–B36 adoption\naudit, the blast-radius dimensions, the five test tiers, the gating model) is\nunchanged, outputs still land under `.gc/contributing/`, and nothing here pushes\na branch or opens a PR. **The only break is the names**, so update any reference\nthat pins one:\n\n| Old name | New name |\n|----------|----------|\n| `contributing` (entry skill) | [`start-contribution`](skills/start-contribution/SKILL.md) |\n| `plan-pr` | [`plan-implementation`](skills/plan-implementation/SKILL.md) |\n| `blast-radius` | [`map-blast-radius`](skills/map-blast-radius/SKILL.md) |\n| `ship` | [`fine-tune`](skills/fine-tune/SKILL.md) |\n| `check` | [`review`](skills/review/SKILL.md) |\n| `mol-contributing-triage` | `mol-contributing-find-work` |\n| `mol-contributing-start` | `mol-contributing-plan-implementation` |\n| `mol-contributing-blast-radius` | `mol-contributing-map-blast-radius` |\n| `mol-contributing-ship` | `mol-contributing-fine-tune` |\n\n(`mol-contributing-review` keeps its name.) To upgrade:\n\n- **Skills (agent-read):** enter at [`start-contribution`](skills/start-contribution/SKILL.md)\n instead of `contributing`; the renamed steps link from there.\n- **Formulas (gc-orchestrated):** update any `gc sling mol-contributing-*` call\n that uses an old formula name per the table above.\n- **New in 0.4.0:** the [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md)\n mayor-mode umbrella, which drives the whole lifecycle by dispatching the\n per-step formulas and gating each one. It's additive — existing per-step and\n agent-read usage keeps working unchanged.\n\n## Pack contents\n\n```\ncontributing/\n├── pack.toml schema=2; no imports (self-contained)\n├── README.md\n├── skills/ agent-read + mayor-mode maps\n│ ├── start-contribution/SKILL.md entry router (GATE 0); the lifecycle map\n│ ├── orchestrate-contribution/SKILL.md mayor loop: dispatch the steps, gate each\n│ ├── write-issue/SKILL.md file a maintainer-grade issue\n│ ├── find-work/SKILL.md triage open issues into a work-queue\n│ ├── plan-implementation/SKILL.md adoption-review-aware implementation planning\n│ ├── map-blast-radius/SKILL.md map the impact surface of a change\n│ ├── fine-tune/SKILL.md pre-push fine-tuning loop (ends with review)\n│ └── review/SKILL.md mechanical gates + the B1–B36 audit\n├── formulas/ gc-orchestrated mode (thin wrappers over the skills)\n│ ├── mol-contributing-find-work.formula.toml -> find-work\n│ ├── mol-contributing-plan-implementation.formula.toml -> plan-implementation\n│ ├── mol-contributing-map-blast-radius.formula.toml -> map-blast-radius\n│ ├── mol-contributing-review.formula.toml -> review\n│ └── mol-contributing-fine-tune.formula.toml -> fine-tune\n├── doctor/ preflight checks (gc, gh, git present)\n│ ├── check-gc.sh + gc/doctor.toml\n│ ├── check-gh.sh + gh/doctor.toml\n│ └── check-git.sh + git/doctor.toml\n└── tests/\n ├── test_contributing_skill_frontmatter.py skills have name + description\n ├── test_contributing_pack_structure.py self-contained; doctor scripts executable\n └── test_contributing_formulas.py formulas parse; each references a real skill\n```\n\n## Tests\n\n```sh\npython3 -m pytest contributing/tests/\n```\n\n[pr-pipeline]: https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline" + "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/contributing-to-gascity/README.md", + "content": "# Contributing\n\nThe external-contributor lifecycle for\n[gastownhall/gascity](https://github.com/gastownhall/gascity), distributed as a\nGas City pack.\n\nIt gives an outside contributor the full journey of landing work upstream — and\ncarries Gas City's **actual** standards at every step. This is not generic PR\ndiscipline: the adoption-review audit (B1–B36), the blast-radius dimensions, the\ndesign-capture rule, the five test tiers, and the code conventions a Gas City\nmaintainer reviews against are all **baked into the skill text**, so your own\ncoding agent applies them by reading the skill.\n\n## Self-contained by design\n\nThis pack has **no imports** and depends on nothing you might not have — no\ninternal agents, no sibling pack, no maintainer-only tooling. Every skill is a\nself-applicable checklist. You run the workflow with `git`, `gh`, and your\ncoding agent reading the skills; that's it.\n\n> The generic [pr-pipeline] pack is still the right choice for a city that wants\n> contributor discipline without Gas City's particular standards. This pack is\n> the gas-city-specific instantiation — it trades portability for baked-in\n> standards.\n\n## The lifecycle\n\nIt starts at a **router** — GATE 0, *\"a priority issue, or your own?\"* — that\nfeeds two step-1 variants (do **1a or 1b** depending on what you're starting\nfrom); both converge on step 2.\n\n| Step | What you do | Skill |\n|------|-------------|-------|\n| 0 | **Start here.** Route on entry: priority issue → 1a; your own issue/bug → 1b | [`start-contribution`](skills/start-contribution/SKILL.md) |\n| 1a | Find a priority issue to work on (someone else's, or a triage pick) | [`find-work`](skills/find-work/SKILL.md) |\n| 1b | Write a high-quality issue (something you found) | [`write-issue`](skills/write-issue/SKILL.md) |\n| 2 | Plan the implementation (incl. blast-radius mapping as Phase 2) | [`plan-implementation`](skills/plan-implementation/SKILL.md) |\n| 3 | Fine-tune the diff (incl. the review as its final gate) | [`fine-tune`](skills/fine-tune/SKILL.md) |\n\nTwo sub-skills are **a phase of a parent skill AND a standalone utility** — not\npipeline steps of their own. Run each as part of its parent, or directly:\n\n- [`map-blast-radius`](skills/map-blast-radius/SKILL.md) — Phase 2 of\n `plan-implementation`; standalone for ad-hoc impact mapping (scoping a refactor,\n mapping a change for someone else).\n- [`review`](skills/review/SKILL.md) — the final gate of `fine-tune`; standalone\n to review any diff/branch against the Gas City standard (incl. before a PR\n exists, or as a maintainer on an incoming contribution).\n\nStep 1 has two mutually-exclusive variants — the router (step 0) picks one, then\nboth lead to step 2:\n\n- **Implement/fix a priority issue** — step 1a (`find-work` to triage), then plan it (step 2).\n- **Implement/fix your own issue** — step 1b (`write-issue`), then plan it (step 2).\n\nThe [`start-contribution`](skills/start-contribution/SKILL.md) skill is the\noperational map and the entry router: it runs GATE 0, branches to 1a or 1b, and\nlinks each step to its skill.\n\n## Three ways to run it\n\nThe same lifecycle, the same standards, three delivery modes:\n\n- **Skills (agent-read)** — your coding agent applies a step by *reading* the\n skill text. This is the default for a contributor working with a coding agent:\n no orchestration, no city required. Start at\n [`start-contribution`](skills/start-contribution/SKILL.md).\n- **Formulas (gc-orchestrated)** — thin `mol-contributing-*` wrappers let a city\n *dispatch* a single step to a transient worker session as a gc formula. Each\n wrapper is orchestration only: it resolves the run's root bead, records state,\n writes the output artifact, and enforces blocking early-exits — then **delegates\n every standard to its sibling skill**. The skill stays the single source of\n truth; the formula never restates the audit, the gates, or the dimensions.\n- **Mayor-orchestrated lifecycle (whole-process)** — the\n [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md) skill\n drives the *whole* lifecycle as a **mayor loop**: it dispatches the per-step\n formulas in order and **pauses at each of the four human gates** (see [The\n gating model](#the-gating-model)). It owns only dispatch + gate; every standard\n stays in the skills, every step's mechanics stay in the formulas. No formula is\n modified — the loop consumes the root-bead notes the formulas already emit.\n\n| Formula | Applies skill | Models on (pr-pipeline) |\n| -------------------------------------- | -------------------------------------------------------------- | ----------------------- |\n| `mol-contributing-find-work` | [`find-work`](skills/find-work/SKILL.md) | `mol-pr-triage` |\n| `mol-contributing-plan-implementation` | [`plan-implementation`](skills/plan-implementation/SKILL.md) | `mol-pr-start` |\n| `mol-contributing-map-blast-radius` | [`map-blast-radius`](skills/map-blast-radius/SKILL.md) | `mol-pr-blast-radius` |\n| `mol-contributing-review` | [`review`](skills/review/SKILL.md) | `mol-pr-review` |\n| `mol-contributing-fine-tune` | [`fine-tune`](skills/fine-tune/SKILL.md) | `mol-pr-ship` |\n\nThere is no formula for `write-issue`: issue authoring sits upstream of the PR\nflow (you have nothing to orchestrate against yet), so use that skill directly.\n\nFormula outputs land under `.gc/contributing/` (work-queue, plan, blast-radius,\nreview, and fine-tune reports), and run state is recorded in the molecule's\nroot-bead notes. Like the skills, the formulas stop before pushing —\n`mol-contributing-fine-tune` ends at the readiness report and never runs\n`git push` or `gh pr create`.\n\n## The gating model\n\nThe lifecycle has **four human gates** — pick an issue (GATE 1), confirm the plan\nbefore any code (GATE 2), review the readiness report (GATE 3), and the entry\nbranch itself (GATE 0). Those gates are **real**, but there is deliberately **no\nsingle end-to-end formula** that runs the whole lifecycle unattended. Here's why.\n\nA `mol-contributing-*` formula runs in an **unattended transient worker session** —\nnobody is at the keyboard. A formula gate can only *auto-proceed* or\n*halt-and-stop*; it cannot interactively pause and resume the same run. But the\ncontributor gates are interactive human decisions, and one step (implementation)\nis done by a human by hand. So the gates are realized at **formula boundaries / the\nmayor layer**, not inside a worker:\n\n- In **per-step formula** mode, each formula ends by writing its artifact and\n printing `Next: dispatch mol-contributing-`. That handoff line **is** a gate —\n a city operator reads the artifact and decides whether to dispatch the next step.\n- In **mayor-orchestrated** mode, [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md)\n formalizes the same boundary into a loop: dispatch a step → read the root-bead\n notes the worker wrote → surface the artifact + recommended action to the human →\n **wait for the decision** → dispatch the next step. The pause works because the\n mayor is the attended agent actually talking to the human. Across long waits it\n uses `gc handoff`, and a fresh mayor resumes from the bead notes.\n\nThe mayor loop never auto-pushes, never opens a PR, and never auto-implements —\nGATE 2, GATE 3, and the final stop are preserved.\n\n## Why two map skills\n\nTwo skills describe the same lifecycle, one per audience:\n\n- [`start-contribution`](skills/start-contribution/SKILL.md) — the **contributor\n entry**. A coding agent reads each step's skill and **implements by hand**. No\n city, no `gc`.\n- [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md) — the\n **mayor umbrella**. A city operator **dispatches** the steps to transient workers\n and gates each one.\n\nThey are split because a single skill would carry one `description` for two very\ndifferent trigger contexts and two registers — \"apply by hand\" vs. \"dispatch +\ngate\" — and would mis-route both. Keeping them separate makes each `description`\nprecise. `orchestrate-contribution` **reuses `start-contribution`'s GATE 0 branch\nlogic rather than restating it**, so the entry skill stays the single source of\ntruth for the map.\n\n## Nothing here pushes for you\n\nEach step produces an artifact you act on — an issue body, a plan, a blast-radius\nreport, an audit verdict, a readiness report. The `fine-tune` skill stops at the\nreadiness report. **Pushing the branch and opening the PR are your call.**\n\n## Usage\n\nImport it into your city from this repository:\n\n```sh\ngc import add https://github.com/gastownhall/gascity-packs.git//contributing-to-gascity\n```\n\nThat writes the import and pins it in `packs.lock`. Without `--version`, `gc`\nresolves the newest repository tag, so this form needs a tag that contains the\n`contributing-to-gascity/` directory. Until one exists, pin a commit on `main`\nwith `--version sha:`.\n\nThe same import by hand in `pack.toml`, followed by `gc import install`:\n\n```toml\n[imports.contributing-to-gascity]\nsource = \"https://github.com/gastownhall/gascity-packs.git//contributing-to-gascity\"\n```\n\nThe same tag rule applies here. Until a tag contains the directory, add\n`version = \"sha:\"` to the table.\n\nTo work on the pack from a local checkout, point `source` at the path instead:\n\n```toml\n[imports.contributing-to-gascity]\nsource = \"../gascity-packs/contributing-to-gascity\"\n```\n\nThen the skills load for your coding agent. You can also read them directly from\nthis directory — they're self-contained Markdown.\n\n## Migrating from the previous version\n\nThe current pack is **0.5.0**.\n\n**0.5.0 is a breaking rename**: the pack moved from `contributing` to\n`contributing-to-gascity` (directory and import name). Update\n`[imports.contributing]` to `[imports.contributing-to-gascity]` and the `source`\npath accordingly. The skill and formula names are unchanged.\n\nIf you adopted an earlier version, the skill and\nformula **names** changed during the lifecycle rework — nothing else about how a\nstep behaves did. Every standard baked into the skills (the B1–B36 adoption\naudit, the blast-radius dimensions, the five test tiers, the gating model) is\nunchanged, outputs still land under `.gc/contributing/`, and nothing here pushes\na branch or opens a PR. **The only break is the names**, so update any reference\nthat pins one:\n\n| Old name | New name |\n|----------|----------|\n| `contributing` (entry skill) | [`start-contribution`](skills/start-contribution/SKILL.md) |\n| `plan-pr` | [`plan-implementation`](skills/plan-implementation/SKILL.md) |\n| `blast-radius` | [`map-blast-radius`](skills/map-blast-radius/SKILL.md) |\n| `ship` | [`fine-tune`](skills/fine-tune/SKILL.md) |\n| `check` | [`review`](skills/review/SKILL.md) |\n| `mol-contributing-triage` | `mol-contributing-find-work` |\n| `mol-contributing-start` | `mol-contributing-plan-implementation` |\n| `mol-contributing-blast-radius` | `mol-contributing-map-blast-radius` |\n| `mol-contributing-ship` | `mol-contributing-fine-tune` |\n\n(`mol-contributing-review` keeps its name.) To upgrade:\n\n- **Skills (agent-read):** enter at [`start-contribution`](skills/start-contribution/SKILL.md)\n instead of `contributing`; the renamed steps link from there.\n- **Formulas (gc-orchestrated):** update any `gc sling mol-contributing-*` call\n that uses an old formula name per the table above.\n- **New in 0.4.0:** the [`orchestrate-contribution`](skills/orchestrate-contribution/SKILL.md)\n mayor-mode umbrella, which drives the whole lifecycle by dispatching the\n per-step formulas and gating each one. It's additive — existing per-step and\n agent-read usage keeps working unchanged.\n\n## Pack contents\n\n```\ncontributing-to-gascity/\n├── pack.toml schema=2; no imports (self-contained)\n├── README.md\n├── skills/ agent-read + mayor-mode maps\n│ ├── start-contribution/SKILL.md entry router (GATE 0); the lifecycle map\n│ ├── orchestrate-contribution/SKILL.md mayor loop: dispatch the steps, gate each\n│ ├── write-issue/SKILL.md file a maintainer-grade issue\n│ ├── find-work/SKILL.md triage open issues into a work-queue\n│ ├── plan-implementation/SKILL.md adoption-review-aware implementation planning\n│ ├── map-blast-radius/SKILL.md map the impact surface of a change\n│ ├── fine-tune/SKILL.md pre-push fine-tuning loop (ends with review)\n│ └── review/SKILL.md mechanical gates + the B1–B36 audit\n├── formulas/ gc-orchestrated mode (thin wrappers over the skills)\n│ ├── mol-contributing-find-work.formula.toml -> find-work\n│ ├── mol-contributing-plan-implementation.formula.toml -> plan-implementation\n│ ├── mol-contributing-map-blast-radius.formula.toml -> map-blast-radius\n│ ├── mol-contributing-review.formula.toml -> review\n│ └── mol-contributing-fine-tune.formula.toml -> fine-tune\n├── doctor/ preflight checks (gc, gh, git present)\n│ ├── check-gc.sh + gc/doctor.toml\n│ ├── check-gh.sh + gh/doctor.toml\n│ └── check-git.sh + git/doctor.toml\n└── tests/\n ├── test_contributing_skill_frontmatter.py skills have name + description\n ├── test_contributing_pack_structure.py self-contained; doctor scripts executable\n └── test_contributing_formulas.py formulas parse; each references a real skill\n```\n\n## Tests\n\n```sh\npython3 -m pytest contributing-to-gascity/tests/\n```\n\n[pr-pipeline]: https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline" }, - "og_image": "/og/contributing.svg", - "latest": "0.2.1", + "og_image": "/og/contributing-to-gascity.svg", + "latest": "0.5.0", "releases": [ { - "version": "0.1.0", + "version": "0.5.0", "ref": "main", - "commit": "16f8297d4e62e143456bfbe4a00e0980a7d9400e", - "hash": "sha256:8ee58fba7a87a2390fe707ccb593eec3f25b303782696d836d800e68992beeef", - "description": "Initial contributing pack release.", - "withdrawn": false - }, - { - "version": "0.2.0", - "ref": "main", - "commit": "7dd57dd9be7d286f19f74c27a00a4359392734a4", - "hash": "sha256:3ba4ad08c81ab8d2cb9bd591a0f546435e14a9132b1d4d4745bde03d0feb94ad", - "description": "Self-contained gas-city contributor lifecycle: drops the pr-pipeline import, bakes in the B-rule adoption-review audit, blast-radius dimensions, and test-tier strategy (7 skills, zero imports).", - "withdrawn": false - }, - { - "version": "0.2.1", - "ref": "main", - "commit": "5b7c5c1320ee6537f5b9c661d2e52712140320df", - "hash": "sha256:3757a3dee523efb29e7cf1f9927c70c9bf491eba6d8416e147430aa6130702cf", - "description": "YAML-safe skill front matter (0.2.0 had an unquoted colon that broke parsing) plus a durability pass — removes time-pinned references (PR-number citations, dated product names, build-timeout numbers, drift-prone counts) so the skills resist staleness. No behavior change.", + "commit": "510a4e22a34d325063fe24f77038c004221b7e7e", + "hash": "sha256:e4d85af1e0e202e30785ac33ac12c83f26dba1cc61de22573d3ac5f8ea9e80de", + "description": "First release under the new name; the pack was renamed from contributing. Carries everything since contributing 0.2.1: the mol-contributing-* formula set, renamed lifecycle skills with an orchestrated umbrella, default-branch resolution from the local ref, and bead commands routed through gc. Imports must switch from //contributing to //contributing-to-gascity.", "withdrawn": false } ] @@ -150,10 +150,10 @@ "description": "Discord services, commands, and prompt fragments for Gas City.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/discord", "source_kind": "git", - "search_text": "discord gascity-packs maintained gas city discord services, commands, and prompt fragments for gas city. https://github.com/gastownhall/gascity-packs/tree/main/discord git # discord pack workspace-hosted discord provider extension for gas city. this pack keeps discord outside core `gc`. the pack owns the discord-facing services, state, commands, and prompt fragments under `.gc/services/discord/`. `discord` replaces the older `discord-intake` pack surface. do not include both packs in the same workspace: they intentionally share the `discord-interactions` and `discord-admin` service identities, so loading both at once will collide. current slices: - discord app metadata import and bot-token storage - public interactions endpoint plus tenant-visible admin/status surface - private gateway worker for inbound dms and bot-mentioned room messages - slash-command `/gc fix` intake with modal-backed summary/context capture - guild/channel/role policy enforcement and guild command sync - workflow status projection back to discord - dm/room chat bindings stored in pack state - normalized `` delivery into exact named sessions - root-room launcher mode with `@@handle` and thread-scoped sessions - explicit `gc discord publish` for human-visible replies through saved bindings - safer `gc discord reply-current` for replying to the latest discord turn in-session - bridge-local room peer fanout after a successful publish - `gc discord retry-peer-fanout` for redriving failed peer targets without reposting to discord - shared prompt fragment at `template-fragments/discord-v0.template.md` launcher rooms and ambient room reads require discord's `message content intent` to be enabled for the app in the developer portal. without that privileged intent, discord can deliver guild message events with empty `content` unless the bot was directly mentioned. ## import it ```toml # pack.toml [imports.discord] source = \"../packs/discord\" ``` ## migration if you currently use `discord-intake`, migrate by switching the workspace include to `discord`, then re-import app credentials and recreate mappings in the new state root: ```bash gc discord import-app ... gc discord map-channel ... gc discord map-rig ... gc discord sync-commands ``` re-running `gc discord sync-commands ` after the migration is required. discord keeps the old registered command shape until you sync again, so the new command schema does not take effect by itself. after the sync, the `/gc fix` `rig` option is no longer required by discord. omitting `rig` now routes by channel mapping when one exists and otherwise fails closed with the normal \"no channel mapping\" rejection. until you re-run `sync-commands`, discord still enforces the old `rig`-required schema at the api layer, so users will see a discord validation error before the new channel-fallback behavior can run. the old pack stored state under `.gc/services/discord-intake/`; this pack uses `.gc/services/discord/`. ## publication after the workspace starts, `gc service list` should show: - `discord-interactions` with public publication - `discord-admin` with tenant publication - `discord-gateway` as a private worker open the tenant-visible `discord-admin` url to get the published interactions url and current app state. ## app import create a discord app in the developer portal, then import the app metadata and bot token: ```bash gc discord import-app \\ --application-id 123456789012345678 \\ --public-key 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \\ --bot-token \"$discord_bot_token\" ``` after import, point the app's interactions endpoint url at: ```text https:///v0/discord/interactions ``` ## slash-command intake map discord channels or rig names to workflow targets: ```bash gc discord map-channel 123456789012345678 223456789012345678 product/polecat \\ --fix-formula mol-discord-fix-issue gc discord map-rig 123456789012345678 mission-control mission-control/polecat ``` the default `mol-discord-fix-issue` workflow expects a `rig/polecat` sling target. if you need a different pool contract, use a different formula. register the guild-scoped `/gc` command after the bot is installed: ```bash gc discord sync-commands 123456789012345678 ``` ## session chat control plane direct bindings still exist for exact session routing: ```bash gc discord bind-dm 123456789012345678 sky gc discord bind-room --guild-id 223456789012345678 323456789012345678 sky lawrence gc discord bind-room --guild-id 223456789012345678 --enable-ambient-read 323456789012345678 sky lawrence gc discord bind-room --guild-id 223456789012345678 --enable-ambient-read --allow-untargeted-ambient-delivery 323456789012345678 randy gc discord bind-room --guild-id 223456789012345678 --enable-peer-fanout 323456789012345678 corp--sky corp--priya ``` launcher mode is the new room-first ux: ```bash gc discord enable-room-launch --guild-id 223456789012345678 323456789012345678 gc discord enable-room-launch --guild-id 223456789012345678 --response-mode respond_all --default-handle corp/sky 323456789012345678 ``` in a launcher room, `@@handle` launches a thread-scoped session for that agent. the session receives the root-room message immediately, but the visible discord thread is only created when the agent explicitly replies with `gc discord reply-current`. publish a human-visible reply through a saved binding: ```bash gc discord publish --binding room:323456789012345678 --body-file ./reply.txt gc discord publish --binding room:323456789012345678 --trigger 423456789012345678 --body \"on it.\" gc discord publish --binding room:323456789012345678 --conversation-id 523456789012345678 --trigger 423456789012345678 --body \"reply in the thread\" gc discord publish --binding room:323456789012345678 --source-event-kind discord_human_message --source-ingress-receipt-id in-423456789012345678 --source-session corp--sky --body-file ./reply.txt # preferred agent reply path for the current discord turn gc discord reply-current --body-file ./reply.txt # repair failed peer deliveries without reposting to discord gc discord retry-peer-fanout discord-publish-123 ``` inbound behavior in v0: - dms to the bot route through the matching `bind-dm` binding - configured launcher rooms accept `@@handle` without a bot mention - launcher rooms may also use `respond_all` with a default qualified handle - in `respond_all` launcher rooms, top-level replies still require an explicit `@@handle` - the first launcher-backed agent reply creates the discord thread automatically - follow-up messages inside that managed thread continue to the same agent session without requiring a bot mention - `@@handle` inside a managed launcher thread retargets the next turn to that agent and creates its thread-local session on demand - replying to an agent-authored discord message inside a managed launcher thread implicitly targets that same agent - unmentioned follow-ups inside a managed launcher thread continue to the last agent the human addressed in that thread - guild and thread messages route only when the bot is explicitly mentioned - ambient-read room bindings are the exception: the bound room or bound thread accepts unmentioned messages, but only when one or more exact `@session_name` targets are present - single-session ambient-read bindings may opt into untargeted delivery with `--allow-untargeted-ambient-delivery`; that sticky room mode delivers all visible messages to the one bound agent, even when the body includes a non-exact shorthand such as `@randy` - thread messages inherit the parent room binding when the thread itself is not bound - inherited thread routing still requires a bot mention unless the thread itself is bound with ambient read - ambient-read rooms stay targeted-only even when the bot is mentioned, unless the binding explicitly allows untargeted ambient delivery for its one bound session - launcher rooms and ambient-read rooms depend on discord `message content intent` because they consume unmentioned guild text - `@sky` inside the message targets that session name exactly - untargeted room messages fan out to every bound participant session - untargeted multi-session ambient-read room messages are ignored instead of broadcasting - agent normal output remains private; only explicit publish commands speak back to humans - agents should prefer `gc discord reply-current --body-file ...` when answering the latest discord turn - direct `gc discord publish` only participates in peer fanout when explicit source metadata is supplied - bot-authored discord messages remain ignored on inbound; peer fanout is local bridge behavior - peer fanout for room publishes is opt-in per binding and disabled by default - peer-triggered publishes only fan out when they explicitly mention target `@session_name` values ## inspect status ```bash gc discord status gc discord status --json ``` ## workflow helper the formula uses the message helper to project status back to discord: ```bash gc discord post-message --request-id dc-123-fix --body \"started work\" ``` 0.1.1 refresh discord pack release pin.", + "search_text": "discord gascity-packs maintained gas city discord services, commands, and prompt fragments for gas city. https://github.com/gastownhall/gascity-packs/tree/main/discord git # discord pack workspace-hosted discord provider extension for gas city. this pack keeps discord outside core `gc`. the pack owns the discord-facing services, state, commands, and prompt fragments under `.gc/services/discord/`. `discord` replaces the older `discord-intake` pack surface. do not include both packs in the same workspace: they intentionally share the `discord-interactions` and `discord-admin` service identities, so loading both at once will collide. current slices: - discord app metadata import and bot-token storage - public interactions endpoint plus tenant-visible admin/status surface - private gateway worker for inbound dms and bot-mentioned room messages - slash-command `/gc fix` intake with modal-backed summary/context capture - guild/channel/role policy enforcement and guild command sync - workflow status projection back to discord - dm/room chat bindings stored in pack state - normalized `` delivery into exact named sessions - root-room launcher mode with `@@handle` and thread-scoped sessions - explicit `gc discord publish` for human-visible replies through saved bindings - safer `gc discord reply-current` for replying to the latest discord turn in-session - bridge-local room peer fanout after a successful publish - `gc discord retry-peer-fanout` for redriving failed peer targets without reposting to discord - shared prompt fragment at `template-fragments/discord-v0.template.md` launcher rooms and ambient room reads require discord's `message content intent` to be enabled for the app in the developer portal. without that privileged intent, discord can deliver guild message events with empty `content` unless the bot was directly mentioned. ## import it ```toml # pack.toml [imports.discord] source = \"../packs/discord\" ``` ## migration if you currently use `discord-intake`, migrate by switching the workspace include to `discord`, then re-import app credentials and recreate mappings in the new state root: ```bash gc discord import-app ... gc discord map-channel ... gc discord map-rig ... gc discord sync-commands ``` re-running `gc discord sync-commands ` after the migration is required. discord keeps the old registered command shape until you sync again, so the new command schema does not take effect by itself. after the sync, the `/gc fix` `rig` option is no longer required by discord. omitting `rig` now routes by channel mapping when one exists and otherwise fails closed with the normal \"no channel mapping\" rejection. until you re-run `sync-commands`, discord still enforces the old `rig`-required schema at the api layer, so users will see a discord validation error before the new channel-fallback behavior can run. the old pack stored state under `.gc/services/discord-intake/`; this pack uses `.gc/services/discord/`. ## publication after the workspace starts, `gc service list` should show: - `discord-interactions` with public publication - `discord-admin` with tenant publication - `discord-gateway` as a private worker open the tenant-visible `discord-admin` url to get the published interactions url and current app state. ## app import create a discord app in the developer portal, then import the app metadata and bot token: ```bash bao kv get -field=bot_token internal/kv/example/agents/default/discord | gc discord import-app \\ --application-id 123456789012345678 \\ --public-key 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \\ --bot-token-file /dev/stdin ``` the command above configures the default app. add separate chat identities with stable lowercase app names: ```bash bao kv get -field=bot_token internal/kv/example/agents/ollie/discord | gc discord import-app \\ --app ollie \\ --application-id 223456789012345678 \\ --public-key abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 \\ --bot-token-file /dev/stdin \\ --guild-allowlist 323456789012345678 \\ --channel-allowlist 423456789012345678 bao kv get -field=bot_token internal/kv/example/agents/olivia/discord | gc discord import-app \\ --app olivia \\ --application-id 523456789012345678 \\ --public-key 123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0 \\ --bot-token-file /dev/stdin \\ --guild-allowlist 323456789012345678 \\ --channel-allowlist 423456789012345678 ``` named apps have independent policy, token files, gateway connections, chat bindings, ingress receipts, and health counters. the default token remains at `secrets/bot-token.txt`; named tokens use `secrets/bot-token-.txt`. all token files are mode `0600`. named imports authenticate the supplied token with discord and reject it before any local mutation unless its bot user id matches `--application-id`. omitted allowlist flags preserve an app's existing policy during credential rotation. an explicitly supplied empty token or token file is an error, so a failed bao read cannot silently update metadata while retaining a stale credential. an app name is pinned to its first `application_id`; import a replacement under a new app name instead of changing that identity in place. config and token persistence share one lock and roll back together if the credential write fails. run `gc service restart discord-gateway` after adding, removing, or rotating named apps and credentials. the gateway starts one connection per configured app and staggers discord identify requests. a failed named connection reconnects independently and does not stop the other bots. before rolling this pack back to a version without multi-app support, back up `.gc/services/discord/data/config.json` and the service secrets directory. older config mutators do not understand the `apps` registry and can remove it when they rewrite schema-v1 config; do not run them until the multi-app config has been restored or intentionally retired. named apps cover direct chat in this release. interactions, `/gc` commands, workflow mappings, and launcher rooms continue to use the default app. after import, point the app's interactions endpoint url at: ```text https:///v0/discord/interactions ``` ## slash-command intake map discord channels or rig names to workflow targets: ```bash gc discord map-channel 123456789012345678 223456789012345678 product/polecat \\ --fix-formula mol-discord-fix-issue gc discord map-rig 123456789012345678 mission-control mission-control/polecat ``` the default `mol-discord-fix-issue` workflow expects a `rig/polecat` sling target. if you need a different pool contract, use a different formula. register the guild-scoped `/gc` command after the bot is installed: ```bash gc discord sync-commands 123456789012345678 ``` ## session chat control plane direct bindings still exist for exact session routing: ```bash gc discord bind-dm 123456789012345678 sky gc discord bind-room --guild-id 223456789012345678 323456789012345678 sky lawrence gc discord bind-room --guild-id 223456789012345678 --enable-ambient-read 323456789012345678 sky lawrence gc discord bind-room --guild-id 223456789012345678 --enable-ambient-read --allow-untargeted-ambient-delivery 323456789012345678 randy gc discord bind-room --guild-id 223456789012345678 --enable-peer-fanout 323456789012345678 corp--sky corp--priya ``` bind several bot identities to the same room by selecting an app for each binding: ```bash gc discord bind-room --app ollie --guild-id 223456789012345678 323456789012345678 teams.lead gc discord bind-room --app olivia --guild-id 223456789012345678 323456789012345678 teams.pm gc discord bind-room --app sky --guild-id 223456789012345678 323456789012345678 teams.designer ``` mentioning a bot routes only through that app's binding. an inbound receipt records the app, and `gc discord reply-current` automatically publishes with the same app and token. for an operator-controlled send, select it explicitly: ```bash gc discord publish --app ollie --binding room:323456789012345678 --body-file ./reply.txt ``` an exact default binding remains the backward-compatible result when `--app` is omitted. if there is no default binding, one matching named binding can be resolved automatically; several named candidates are ambiguous and fail closed. guild and parent-channel policy applies to both the default app and named apps. room bind and publish verify discord's actual channel scope with the selected bot token; `--guild-id` is a consistency hint, not an authority boundary. launcher mode is the new room-first ux: ```bash gc discord enable-room-launch --guild-id 223456789012345678 323456789012345678 gc discord enable-room-launch --guild-id 223456789012345678 --response-mode respond_all --default-handle corp/sky 323456789012345678 ``` in a launcher room, `@@handle` launches a thread-scoped session for that agent. the session receives the root-room message immediately, but the visible discord thread is only created when the agent explicitly replies with `gc discord reply-current`. publish a human-visible reply through a saved binding: ```bash gc discord publish --binding room:323456789012345678 --body-file ./reply.txt gc discord publish --binding room:323456789012345678 --trigger 423456789012345678 --body \"on it.\" gc discord publish --binding room:323456789012345678 --conversation-id 523456789012345678 --trigger 423456789012345678 --body \"reply in the thread\" gc discord publish --binding room:323456789012345678 --source-event-kind discord_human_message --source-ingress-receipt-id in-423456789012345678 --source-session corp--sky --body-file ./reply.txt # preferred agent reply path for the current discord turn gc discord reply-current --body-file ./reply.txt # repair failed peer deliveries without reposting to discord gc discord retry-peer-fanout discord-publish-123 ``` inbound behavior in v0: - dms to the bot route through the matching `bind-dm` binding - configured launcher rooms accept `@@handle` without a bot mention - launcher rooms may also use `respond_all` with a default qualified handle - in `respond_all` launcher rooms, top-level replies still require an explicit `@@handle` - the first launcher-backed agent reply creates the discord thread automatically - follow-up messages inside that managed thread continue to the same agent session without requiring a bot mention - `@@handle` inside a managed launcher thread retargets the next turn to that agent and creates its thread-local session on demand - replying to an agent-authored discord message inside a managed launcher thread implicitly targets that same agent - unmentioned follow-ups inside a managed launcher thread continue to the last agent the human addressed in that thread - guild and thread messages route only when the bot is explicitly mentioned - ambient-read room bindings are the exception: the bound room or bound thread accepts unmentioned messages, but only when one or more exact `@session_name` targets are present - single-session ambient-read bindings may opt into untargeted delivery with `--allow-untargeted-ambient-delivery`; that sticky room mode delivers all visible messages to the one bound agent, even when the body includes a non-exact shorthand such as `@randy` - thread messages inherit the parent room binding when the thread itself is not bound - inherited thread routing still requires a bot mention unless the thread itself is bound with ambient read - ambient-read rooms stay targeted-only even when the bot is mentioned, unless the binding explicitly allows untargeted ambient delivery for its one bound session - launcher rooms and ambient-read rooms depend on discord `message content intent` because they consume unmentioned guild text - `@sky` inside the message targets that session name exactly - untargeted room messages fan out to every bound participant session - untargeted multi-session ambient-read room messages are ignored instead of broadcasting - agent normal output remains private; only explicit publish commands speak back to humans - agents should prefer `gc discord reply-current --body-file ...` when answering the latest discord turn - direct `gc discord publish` only participates in peer fanout when explicit source metadata is supplied - bot-authored discord messages remain ignored on inbound; peer fanout is local bridge behavior - peer fanout for room publishes is opt-in per binding and disabled by default - peer-triggered publishes only fan out when they explicitly mention target `@session_name` values ## reading an inbound message every inbound message is written to `.gc/services/discord/data/chat-ingress/in-.json` before it is routed. four fields describe the body: | field | what it is | | --- | --- | | `body` | the whole message, bot mention removed, line breaks as typed | | `body_length` | `len(body)` | | `body_preview` | `body` flattened to one line and clipped to 160 chars for status lines | | `body_truncated` | `true` when the 160-char clip dropped something. it is computed from the flattened preview, so a heavily line-broken body can be `false` while `body` and `body_preview` still differ in shape. | **read `body`. `body_preview` is for status lines.** they differ whenever `body_truncated` is true, and a clipped preview does not announce itself — one that happens to end on a sentence boundary reads exactly like a whole message. that cost the mayor two acted-on fragments on 2026-07-30, one of which dropped the scope limit on the work it was authorizing. records written before this shape existed have only `body_preview` and no `body_truncated`. a missing flag means \"unknown\", not \"short\" — for those, the closest signal is `message_debug.gateway_content_length`, which is an upper bound — it is measured before the bot mention is stripped. the session receives the same message a second time in the deferred `` reminder's `untrusted_body_json`. that copy is never truncated, but it is flattened to a single line, so `body` is the only copy that keeps the author's line breaks. it also lands a turn or more later, so anything watching the ingress directory sees the file first. ## inspect status ```bash gc discord status gc discord status --json ``` status lists the default and named apps separately, including token presence, gateway state, and per-app counters. the gateway endpoint also reports an aggregate state without letting one failed bot hide or take down healthy bots. status never prints token values. `recent chat ingress` previews are redacted on this surface, so the line never shows message text. it does append `[clipped — n chars total; ...]` when the record was clipped, which is how you tell a partial record from a whole one without reading the body. ## workflow helper the formula uses the message helper to project status back to discord: ```bash gc discord post-message --request-id dc-123-fix --body \"started work\" ``` 0.1.1 refresh discord pack release pin.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/discord/README.md", - "content": "# Discord Pack\n\nWorkspace-hosted Discord provider extension for Gas City.\n\nThis pack keeps Discord outside core `gc`. The pack owns the Discord-facing\nservices, state, commands, and prompt fragments under `.gc/services/discord/`.\n\n`discord` replaces the older `discord-intake` pack surface. Do not include\nboth packs in the same workspace: they intentionally share the\n`discord-interactions` and `discord-admin` service identities, so loading both\nat once will collide.\n\nCurrent slices:\n\n- Discord app metadata import and bot-token storage\n- public Interactions endpoint plus tenant-visible admin/status surface\n- private gateway worker for inbound DMs and bot-mentioned room messages\n- slash-command `/gc fix` intake with modal-backed summary/context capture\n- guild/channel/role policy enforcement and guild command sync\n- workflow status projection back to Discord\n- DM/room chat bindings stored in pack state\n- normalized `` delivery into exact named sessions\n- root-room launcher mode with `@@handle` and thread-scoped sessions\n- explicit `gc discord publish` for human-visible replies through saved bindings\n- safer `gc discord reply-current` for replying to the latest Discord turn in-session\n- bridge-local room peer fanout after a successful publish\n- `gc discord retry-peer-fanout` for redriving failed peer targets without reposting to Discord\n- shared prompt fragment at `template-fragments/discord-v0.template.md`\n\nLauncher rooms and ambient room reads require Discord's `Message Content Intent`\nto be enabled for the app in the Developer Portal. Without that privileged\nintent, Discord can deliver guild message events with empty `content` unless\nthe bot was directly mentioned.\n\n## Import It\n\n```toml\n# pack.toml\n[imports.discord]\nsource = \"../packs/discord\"\n```\n\n## Migration\n\nIf you currently use `discord-intake`, migrate by switching the workspace\ninclude to `discord`, then re-import app credentials and recreate mappings in\nthe new state root:\n\n```bash\ngc discord import-app ...\ngc discord map-channel ...\ngc discord map-rig ...\ngc discord sync-commands \n```\n\nRe-running `gc discord sync-commands ` after the migration is\nrequired. Discord keeps the old registered command shape until you sync again,\nso the new command schema does not take effect by itself. After the sync, the\n`/gc fix` `rig` option is no longer required by Discord. Omitting `rig` now\nroutes by channel mapping when one exists and otherwise fails closed with the\nnormal \"no channel mapping\" rejection.\nUntil you re-run `sync-commands`, Discord still enforces the old `rig`-required\nschema at the API layer, so users will see a Discord validation error before\nthe new channel-fallback behavior can run.\n\nThe old pack stored state under `.gc/services/discord-intake/`; this pack uses\n`.gc/services/discord/`.\n\n## Publication\n\nAfter the workspace starts, `gc service list` should show:\n\n- `discord-interactions` with public publication\n- `discord-admin` with tenant publication\n- `discord-gateway` as a private worker\n\nOpen the tenant-visible `discord-admin` URL to get the published interactions\nURL and current app state.\n\n## App Import\n\nCreate a Discord app in the Developer Portal, then import the app metadata and\nbot token:\n\n```bash\ngc discord import-app \\\n --application-id 123456789012345678 \\\n --public-key 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \\\n --bot-token \"$DISCORD_BOT_TOKEN\"\n```\n\nAfter import, point the app's Interactions Endpoint URL at:\n\n```text\nhttps:///v0/discord/interactions\n```\n\n## Slash-Command Intake\n\nMap Discord channels or rig names to workflow targets:\n\n```bash\ngc discord map-channel 123456789012345678 223456789012345678 product/polecat \\\n --fix-formula mol-discord-fix-issue\n\ngc discord map-rig 123456789012345678 mission-control mission-control/polecat\n```\n\nThe default `mol-discord-fix-issue` workflow expects a `rig/polecat` sling\ntarget. If you need a different pool contract, use a different formula.\n\nRegister the guild-scoped `/gc` command after the bot is installed:\n\n```bash\ngc discord sync-commands 123456789012345678\n```\n\n## Session Chat Control Plane\n\nDirect bindings still exist for exact session routing:\n\n```bash\ngc discord bind-dm 123456789012345678 sky\ngc discord bind-room --guild-id 223456789012345678 323456789012345678 sky lawrence\ngc discord bind-room --guild-id 223456789012345678 --enable-ambient-read 323456789012345678 sky lawrence\ngc discord bind-room --guild-id 223456789012345678 --enable-ambient-read --allow-untargeted-ambient-delivery 323456789012345678 randy\ngc discord bind-room --guild-id 223456789012345678 --enable-peer-fanout 323456789012345678 corp--sky corp--priya\n```\n\nLauncher mode is the new room-first UX:\n\n```bash\ngc discord enable-room-launch --guild-id 223456789012345678 323456789012345678\ngc discord enable-room-launch --guild-id 223456789012345678 --response-mode respond_all --default-handle corp/sky 323456789012345678\n```\n\nIn a launcher room, `@@handle` launches a thread-scoped session for that agent.\nThe session receives the root-room message immediately, but the visible Discord\nthread is only created when the agent explicitly replies with\n`gc discord reply-current`.\n\nPublish a human-visible reply through a saved binding:\n\n```bash\ngc discord publish --binding room:323456789012345678 --body-file ./reply.txt\ngc discord publish --binding room:323456789012345678 --trigger 423456789012345678 --body \"On it.\"\ngc discord publish --binding room:323456789012345678 --conversation-id 523456789012345678 --trigger 423456789012345678 --body \"Reply in the thread\"\ngc discord publish --binding room:323456789012345678 --source-event-kind discord_human_message --source-ingress-receipt-id in-423456789012345678 --source-session corp--sky --body-file ./reply.txt\n\n# Preferred agent reply path for the current Discord turn\ngc discord reply-current --body-file ./reply.txt\n\n# Repair failed peer deliveries without reposting to Discord\ngc discord retry-peer-fanout discord-publish-123\n```\n\nInbound behavior in v0:\n\n- DMs to the bot route through the matching `bind-dm` binding\n- configured launcher rooms accept `@@handle` without a bot mention\n- launcher rooms may also use `respond_all` with a default qualified handle\n- in `respond_all` launcher rooms, top-level replies still require an explicit `@@handle`\n- the first launcher-backed agent reply creates the Discord thread automatically\n- follow-up messages inside that managed thread continue to the same agent session without requiring a bot mention\n- `@@handle` inside a managed launcher thread retargets the next turn to that agent and creates its thread-local session on demand\n- replying to an agent-authored Discord message inside a managed launcher thread implicitly targets that same agent\n- unmentioned follow-ups inside a managed launcher thread continue to the last agent the human addressed in that thread\n- guild and thread messages route only when the bot is explicitly mentioned\n- ambient-read room bindings are the exception: the bound room or bound thread accepts unmentioned messages, but only when one or more exact `@session_name` targets are present\n- single-session ambient-read bindings may opt into untargeted delivery with `--allow-untargeted-ambient-delivery`; that sticky room mode delivers all visible messages to the one bound agent, even when the body includes a non-exact shorthand such as `@randy`\n- thread messages inherit the parent room binding when the thread itself is not bound\n- inherited thread routing still requires a bot mention unless the thread itself is bound with ambient read\n- ambient-read rooms stay targeted-only even when the bot is mentioned, unless the binding explicitly allows untargeted ambient delivery for its one bound session\n- launcher rooms and ambient-read rooms depend on Discord `Message Content Intent` because they consume unmentioned guild text\n- `@sky` inside the message targets that session name exactly\n- untargeted room messages fan out to every bound participant session\n- untargeted multi-session ambient-read room messages are ignored instead of broadcasting\n- agent normal output remains private; only explicit publish commands speak back to humans\n- agents should prefer `gc discord reply-current --body-file ...` when answering the latest Discord turn\n- direct `gc discord publish` only participates in peer fanout when explicit source metadata is supplied\n- bot-authored Discord messages remain ignored on inbound; peer fanout is local bridge behavior\n- peer fanout for room publishes is opt-in per binding and disabled by default\n- peer-triggered publishes only fan out when they explicitly mention target `@session_name` values\n\n## Inspect Status\n\n```bash\ngc discord status\ngc discord status --json\n```\n\n## Workflow Helper\n\nThe formula uses the message helper to project status back to Discord:\n\n```bash\ngc discord post-message --request-id dc-123-fix --body \"Started work\"\n```" + "content": "# Discord Pack\n\nWorkspace-hosted Discord provider extension for Gas City.\n\nThis pack keeps Discord outside core `gc`. The pack owns the Discord-facing\nservices, state, commands, and prompt fragments under `.gc/services/discord/`.\n\n`discord` replaces the older `discord-intake` pack surface. Do not include\nboth packs in the same workspace: they intentionally share the\n`discord-interactions` and `discord-admin` service identities, so loading both\nat once will collide.\n\nCurrent slices:\n\n- Discord app metadata import and bot-token storage\n- public Interactions endpoint plus tenant-visible admin/status surface\n- private gateway worker for inbound DMs and bot-mentioned room messages\n- slash-command `/gc fix` intake with modal-backed summary/context capture\n- guild/channel/role policy enforcement and guild command sync\n- workflow status projection back to Discord\n- DM/room chat bindings stored in pack state\n- normalized `` delivery into exact named sessions\n- root-room launcher mode with `@@handle` and thread-scoped sessions\n- explicit `gc discord publish` for human-visible replies through saved bindings\n- safer `gc discord reply-current` for replying to the latest Discord turn in-session\n- bridge-local room peer fanout after a successful publish\n- `gc discord retry-peer-fanout` for redriving failed peer targets without reposting to Discord\n- shared prompt fragment at `template-fragments/discord-v0.template.md`\n\nLauncher rooms and ambient room reads require Discord's `Message Content Intent`\nto be enabled for the app in the Developer Portal. Without that privileged\nintent, Discord can deliver guild message events with empty `content` unless\nthe bot was directly mentioned.\n\n## Import It\n\n```toml\n# pack.toml\n[imports.discord]\nsource = \"../packs/discord\"\n```\n\n## Migration\n\nIf you currently use `discord-intake`, migrate by switching the workspace\ninclude to `discord`, then re-import app credentials and recreate mappings in\nthe new state root:\n\n```bash\ngc discord import-app ...\ngc discord map-channel ...\ngc discord map-rig ...\ngc discord sync-commands \n```\n\nRe-running `gc discord sync-commands ` after the migration is\nrequired. Discord keeps the old registered command shape until you sync again,\nso the new command schema does not take effect by itself. After the sync, the\n`/gc fix` `rig` option is no longer required by Discord. Omitting `rig` now\nroutes by channel mapping when one exists and otherwise fails closed with the\nnormal \"no channel mapping\" rejection.\nUntil you re-run `sync-commands`, Discord still enforces the old `rig`-required\nschema at the API layer, so users will see a Discord validation error before\nthe new channel-fallback behavior can run.\n\nThe old pack stored state under `.gc/services/discord-intake/`; this pack uses\n`.gc/services/discord/`.\n\n## Publication\n\nAfter the workspace starts, `gc service list` should show:\n\n- `discord-interactions` with public publication\n- `discord-admin` with tenant publication\n- `discord-gateway` as a private worker\n\nOpen the tenant-visible `discord-admin` URL to get the published interactions\nURL and current app state.\n\n## App Import\n\nCreate a Discord app in the Developer Portal, then import the app metadata and\nbot token:\n\n```bash\nbao kv get -field=bot_token internal/kv/example/agents/default/discord |\n gc discord import-app \\\n --application-id 123456789012345678 \\\n --public-key 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \\\n --bot-token-file /dev/stdin\n```\n\nThe command above configures the default app. Add separate chat identities\nwith stable lowercase app names:\n\n```bash\nbao kv get -field=bot_token internal/kv/example/agents/ollie/discord |\n gc discord import-app \\\n --app ollie \\\n --application-id 223456789012345678 \\\n --public-key abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 \\\n --bot-token-file /dev/stdin \\\n --guild-allowlist 323456789012345678 \\\n --channel-allowlist 423456789012345678\n\nbao kv get -field=bot_token internal/kv/example/agents/olivia/discord |\n gc discord import-app \\\n --app olivia \\\n --application-id 523456789012345678 \\\n --public-key 123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0 \\\n --bot-token-file /dev/stdin \\\n --guild-allowlist 323456789012345678 \\\n --channel-allowlist 423456789012345678\n```\n\nNamed apps have independent policy, token files, gateway connections, chat\nbindings, ingress receipts, and health counters. The default token remains at\n`secrets/bot-token.txt`; named tokens use\n`secrets/bot-token-.txt`. All token files are mode `0600`.\nNamed imports authenticate the supplied token with Discord and reject it before\nany local mutation unless its bot user ID matches `--application-id`. Omitted\nallowlist flags preserve an app's existing policy during credential rotation.\nAn explicitly supplied empty token or token file is an error, so a failed Bao\nread cannot silently update metadata while retaining a stale credential.\nAn app name is pinned to its first `application_id`; import a replacement under\na new app name instead of changing that identity in place. Config and token\npersistence share one lock and roll back together if the credential write\nfails.\n\nRun `gc service restart discord-gateway` after adding, removing, or rotating\nnamed apps and credentials.\nThe gateway starts one connection per configured app and staggers Discord\nidentify requests. A failed named connection reconnects independently and does\nnot stop the other bots.\n\nBefore rolling this pack back to a version without multi-app support, back up\n`.gc/services/discord/data/config.json` and the service secrets directory.\nOlder config mutators do not understand the `apps` registry and can remove it\nwhen they rewrite schema-v1 config; do not run them until the multi-app config\nhas been restored or intentionally retired.\n\nNamed apps cover direct chat in this release. Interactions, `/gc` commands,\nworkflow mappings, and launcher rooms continue to use the default app.\n\nAfter import, point the app's Interactions Endpoint URL at:\n\n```text\nhttps:///v0/discord/interactions\n```\n\n## Slash-Command Intake\n\nMap Discord channels or rig names to workflow targets:\n\n```bash\ngc discord map-channel 123456789012345678 223456789012345678 product/polecat \\\n --fix-formula mol-discord-fix-issue\n\ngc discord map-rig 123456789012345678 mission-control mission-control/polecat\n```\n\nThe default `mol-discord-fix-issue` workflow expects a `rig/polecat` sling\ntarget. If you need a different pool contract, use a different formula.\n\nRegister the guild-scoped `/gc` command after the bot is installed:\n\n```bash\ngc discord sync-commands 123456789012345678\n```\n\n## Session Chat Control Plane\n\nDirect bindings still exist for exact session routing:\n\n```bash\ngc discord bind-dm 123456789012345678 sky\ngc discord bind-room --guild-id 223456789012345678 323456789012345678 sky lawrence\ngc discord bind-room --guild-id 223456789012345678 --enable-ambient-read 323456789012345678 sky lawrence\ngc discord bind-room --guild-id 223456789012345678 --enable-ambient-read --allow-untargeted-ambient-delivery 323456789012345678 randy\ngc discord bind-room --guild-id 223456789012345678 --enable-peer-fanout 323456789012345678 corp--sky corp--priya\n```\n\nBind several bot identities to the same room by selecting an app for each\nbinding:\n\n```bash\ngc discord bind-room --app ollie --guild-id 223456789012345678 323456789012345678 teams.lead\ngc discord bind-room --app olivia --guild-id 223456789012345678 323456789012345678 teams.pm\ngc discord bind-room --app sky --guild-id 223456789012345678 323456789012345678 teams.designer\n```\n\nMentioning a bot routes only through that app's binding. An inbound receipt\nrecords the app, and `gc discord reply-current` automatically publishes with\nthe same app and token. For an operator-controlled send, select it explicitly:\n\n```bash\ngc discord publish --app ollie --binding room:323456789012345678 --body-file ./reply.txt\n```\n\nAn exact default binding remains the backward-compatible result when `--app`\nis omitted. If there is no default binding, one matching named binding can be\nresolved automatically; several named candidates are ambiguous and fail\nclosed.\n\nGuild and parent-channel policy applies to both the default app and named apps.\nRoom bind and publish verify Discord's actual channel scope with the selected\nbot token; `--guild-id` is a consistency hint, not an authority boundary.\n\nLauncher mode is the new room-first UX:\n\n```bash\ngc discord enable-room-launch --guild-id 223456789012345678 323456789012345678\ngc discord enable-room-launch --guild-id 223456789012345678 --response-mode respond_all --default-handle corp/sky 323456789012345678\n```\n\nIn a launcher room, `@@handle` launches a thread-scoped session for that agent.\nThe session receives the root-room message immediately, but the visible Discord\nthread is only created when the agent explicitly replies with\n`gc discord reply-current`.\n\nPublish a human-visible reply through a saved binding:\n\n```bash\ngc discord publish --binding room:323456789012345678 --body-file ./reply.txt\ngc discord publish --binding room:323456789012345678 --trigger 423456789012345678 --body \"On it.\"\ngc discord publish --binding room:323456789012345678 --conversation-id 523456789012345678 --trigger 423456789012345678 --body \"Reply in the thread\"\ngc discord publish --binding room:323456789012345678 --source-event-kind discord_human_message --source-ingress-receipt-id in-423456789012345678 --source-session corp--sky --body-file ./reply.txt\n\n# Preferred agent reply path for the current Discord turn\ngc discord reply-current --body-file ./reply.txt\n\n# Repair failed peer deliveries without reposting to Discord\ngc discord retry-peer-fanout discord-publish-123\n```\n\nInbound behavior in v0:\n\n- DMs to the bot route through the matching `bind-dm` binding\n- configured launcher rooms accept `@@handle` without a bot mention\n- launcher rooms may also use `respond_all` with a default qualified handle\n- in `respond_all` launcher rooms, top-level replies still require an explicit `@@handle`\n- the first launcher-backed agent reply creates the Discord thread automatically\n- follow-up messages inside that managed thread continue to the same agent session without requiring a bot mention\n- `@@handle` inside a managed launcher thread retargets the next turn to that agent and creates its thread-local session on demand\n- replying to an agent-authored Discord message inside a managed launcher thread implicitly targets that same agent\n- unmentioned follow-ups inside a managed launcher thread continue to the last agent the human addressed in that thread\n- guild and thread messages route only when the bot is explicitly mentioned\n- ambient-read room bindings are the exception: the bound room or bound thread accepts unmentioned messages, but only when one or more exact `@session_name` targets are present\n- single-session ambient-read bindings may opt into untargeted delivery with `--allow-untargeted-ambient-delivery`; that sticky room mode delivers all visible messages to the one bound agent, even when the body includes a non-exact shorthand such as `@randy`\n- thread messages inherit the parent room binding when the thread itself is not bound\n- inherited thread routing still requires a bot mention unless the thread itself is bound with ambient read\n- ambient-read rooms stay targeted-only even when the bot is mentioned, unless the binding explicitly allows untargeted ambient delivery for its one bound session\n- launcher rooms and ambient-read rooms depend on Discord `Message Content Intent` because they consume unmentioned guild text\n- `@sky` inside the message targets that session name exactly\n- untargeted room messages fan out to every bound participant session\n- untargeted multi-session ambient-read room messages are ignored instead of broadcasting\n- agent normal output remains private; only explicit publish commands speak back to humans\n- agents should prefer `gc discord reply-current --body-file ...` when answering the latest Discord turn\n- direct `gc discord publish` only participates in peer fanout when explicit source metadata is supplied\n- bot-authored Discord messages remain ignored on inbound; peer fanout is local bridge behavior\n- peer fanout for room publishes is opt-in per binding and disabled by default\n- peer-triggered publishes only fan out when they explicitly mention target `@session_name` values\n\n## Reading An Inbound Message\n\nEvery inbound message is written to `.gc/services/discord/data/chat-ingress/in-.json`\nbefore it is routed. Four fields describe the body:\n\n| field | what it is |\n| --- | --- |\n| `body` | the whole message, bot mention removed, line breaks as typed |\n| `body_length` | `len(body)` |\n| `body_preview` | `body` flattened to one line and clipped to 160 chars for status lines |\n| `body_truncated` | `true` when the 160-char clip dropped something. It is computed from the flattened preview, so a heavily line-broken body can be `false` while `body` and `body_preview` still differ in shape. |\n\n**Read `body`. `body_preview` is for status lines.** They differ whenever\n`body_truncated` is true, and a clipped preview does not announce itself — one\nthat happens to end on a sentence boundary reads exactly like a whole message.\nThat cost the mayor two acted-on fragments on 2026-07-30, one of which dropped\nthe scope limit on the work it was authorizing.\n\nRecords written before this shape existed have only `body_preview` and no\n`body_truncated`. A missing flag means \"unknown\", not \"short\" — for those, the\nclosest signal is `message_debug.gateway_content_length`, which is an upper\nbound — it is measured before the bot mention is stripped.\n\nThe session receives the same message a second time in the deferred\n`` reminder's `untrusted_body_json`. That copy is never\ntruncated, but it is flattened to a single line, so `body` is the only copy\nthat keeps the author's line breaks. It also lands a turn or more later, so\nanything watching the ingress directory sees the file first.\n\n## Inspect Status\n\n```bash\ngc discord status\ngc discord status --json\n```\n\nStatus lists the default and named apps separately, including token presence,\ngateway state, and per-app counters. The gateway endpoint also reports an\naggregate state without letting one failed bot hide or take down healthy bots.\nStatus never prints token values.\n\n`Recent Chat Ingress` previews are redacted on this surface, so the line never\nshows message text. It does append `[CLIPPED — N chars total; ...]` when the\nrecord was clipped, which is how you tell a partial record from a whole one\nwithout reading the body.\n\n## Workflow Helper\n\nThe formula uses the message helper to project status back to Discord:\n\n```bash\ngc discord post-message --request-id dc-123-fix --body \"Started work\"\n```" }, "og_image": "/og/discord.svg", "latest": "0.1.1", @@ -177,10 +177,10 @@ "description": "Gas City planning and implementation workflow pack.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/gascity", "source_kind": "git", - "search_text": "gascity gascity-packs maintained gas city gas city planning and implementation workflow pack. https://github.com/gastownhall/gascity-packs/tree/main/gascity git # gas city build pack (`gc`) this is the base pack for running full software-delivery workflows in gas city: gather requirements, write and review a plan, decompose into tasks, implement in parallel agent sessions, review the result, and optionally publish. it ships three things: - **workflow formulas** — `build-basic` (the starter factory), the `build-from-*` continuation entrypoints, direct `implement`, and github adapter workflows (issue triage, issue fix, pr review). - **the `gc.mayor` coordinator skill** — a user-facing planner that gathers requirements, writes plans, creates approved beads/convoys, and launches the right formula for you. - **the `build-base` contract** — the virtual stage sequence that the methodology packs in this repository (bmad, compound-engineering, superpowers, gstack) extend and override. ## quick start: your first build prerequisites: gas city installed and a city running (`gc init`, `gc start`), and your project added as a rig (`gc rig add .` inside the repo). see the [repository readme](../readme.md) for the from-scratch path. 1. import formulas, claim command, and rig roles. from city directory: ```sh gc import add --name gc https://github.com/gastownhall/gascity-packs.git//gascity ``` then add rig-scoped roles in `city.toml` and run `gc import install`: ```toml [[rigs]] name = \"your-project\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` both imports are required. the rig-scoped roles pack supplies agents but, by design, rig imports do not register city commands; importing roles alone renders prompts that reference `gc gc claim` without installing that command. keep the top-level gas city pack imported at city scope. (contributors hacking on packs can point this source at a local clone.) 2. create a bead describing what you want built, and sling the starter factory at it: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on build-basic \\ --var artifact_root=plans/json-flag/build ``` 3. watch it run. the workflow walks requirements → plan → plan review → decompose → parallel implementation → a three-lane starter review (acceptance, test evidence, simplicity) → finalize. attach to any agent session with `gc session attach `, or inspect the workflow root bead as stages record their artifacts. 4. read the results under `artifact_root` in your rig: `requirements.md`, the implementation plan, review reports, and `factory-run.md` — a short summary of what ran, what was proven, and the suggested next action. prefer a guided experience? run the coordinator skill instead and let it do the planning and launching: ```text use skill gc.mayor ``` ## choosing an entrypoint | you have | launch | notes | | -------- | ------ | ----- | | just an idea | `build-basic` (targeted at a bead) | full lifecycle from requirements onward. | | approved requirements | `build-from-plan` | produces plan + plan review, then continues. | | approved requirements, plan, and plan review | `build-from-decompose` | starts at decomposition. | | an implementation convoy | `build-from-convoy` | drains the convoy, then reviews. | | implementation evidence | `build-from-review` | review, repair/restart handoff, finalize, publish. | | an approved convoy, no build wrapper wanted | `implement` | direct drain without the review/publish suffix. | | a github issue or pr url | `github-issue-triage`, `github-issue-fix`, `github-pr-review` | targetless adapters; see github adapter workflows below. | discover everything that is launchable from the active rig/city context: ```sh gc formula catalog --json gc formula show implement --json ``` launch implementation directly against an approved implementation convoy: ```sh gc sling gc.run-operator --on implement \\ --var context_path= \\ --var drain_policy=separate ``` ## launch variables that matter every build entrypoint accepts the same knobs. set them with `--var k=v` at launch or pin them in a rig's `formula_vars`: | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | (required) | directory under the rig where all build artifacts land. | | `interaction_mode` | `interactive` | `interactive` keeps blocking questions and approval menus; `autonomous` decides and records evidence; `headless` never prompts. | | `review_mode` | `agent` | `report` is read-only findings; `agent` is a structured fix handoff; `interactive` may apply safe fixes directly. | | `drain_policy` | `separate` | `separate` runs implementation beads in parallel worker sessions; `same-session` runs them serially in one shared session. | | `implementation_target` | `gc.implementation-worker` | the rig role that implements each work item. | | `push` / `open_pr` | `false` | allow the publish stage to push and open a pr after all checks pass. | | `max_iterations` | `10` | bound on implementation/review fix attempts. | when variables are not enough, every stage prompt can be replaced by dropping a markdown file at the same relative path in a higher-priority layer, and every documented step can be swapped for your own expansion — see \"stable workflow override interface\" below for the recipe and per-stage examples. if part of the factory already ran, launch the targetless continuation entrypoint that matches the artifacts you already have: ```sh gc sling gc.run-operator build-from-decompose --formula \\ --var artifact_root= \\ --var requirements_path=/requirements.md \\ --var plan_path=/implementation-plan.md \\ --var plan_review_path=/plan-review.md \\ --var drain_policy=separate ``` each continuation validates the artifacts you hand it before doing any work, records the entrypoint it started from, and refuses to finalize as a pass when review or repair is still blocked — see \"choosing an entrypoint\" above for which one matches the artifacts you have. every formula in this pack uses `contract = \"graph.v2\"`. targeted formulas take the core-injected reserved convoy target; they do not declare `issue`, `bead_id`, or `convoy_id` variables. `drain_policy=separate` is the standalone default. use `same-session` only when preserving one shared worktree and conversation is explicitly desired and core shared drain support is available. the pack ships its city-scoped claim command alongside formulas, plus providerless rig role agents under `gascity/roles`. standalone use requires the top-level `gc` import for formulas, mayor skill, and `gc gc claim`, and `gascity/roles` on each target rig for `gc.*` role agents. import roles for each target rig. by default agents inherit city/workspace provider; advanced users can patch individual roles without overriding formulas: ```toml [[rigs]] name = \"my-repo\" [rigs.imports.gc] source = \"/path/to/gascity/roles\" [[rigs.patches]] agent = \"gc.implementation-worker\" provider = \"your-provider\" ``` launch formulas from target rig context, or pass normal `--rig ` selection so `gc.run-operator` resolves to rig-local role. default formula routes use these qualified targets: `gc.run-operator`, `gc.requirements-planner`, `gc.design-author`, `gc.task-decomposer`, `gc.issue-triager`, `gc.design-implementation-reviewer`, `gc.design-test-risk-reviewer`, `gc.review-synthesizer`, `gc.implementation-worker`, `gc.gap-analyst`, `gc.implementation-reviewer`, and `gc.publisher`. ## build methodology contract `build-base` is the virtual full-lifecycle workflow contract. it defines the stable stage sequence that concrete build methodology packs can override: ```text prepare -> requirements -> plan -> plan-review -> decompose -> implement | implement-same-session -> review -> finalize -> publish ``` `build-base` is internal and should not be launched directly. use `build-basic` for the default gas city implementation. it maps the base stages onto the existing gas city requirements, implementation-plan, design-review, create-beads, implementation, post-implementation review, and publish helpers. gap-analysis is a review lens inside the post-implementation review loop, so coverage findings are synthesized and fixed with the rest of the review output. continuation bases are nested suffixes. each base validates its prerequisite inputs, performs one stage or handoff, and delegates to the next suffix: ```text build-from-requirements-base -> build-from-plan-base -> build-from-decompose-base -> build-from-convoy-base -> build-from-review-base ``` the cataloged `build-from-*` formulas are thin default gas city wrappers around those bases. methodology packs that want the same entrypoints should extend the matching `build-from-*-base` formula and override selector defaults, routes, drain item formulas, or review expansions instead of copying the suffix graph. third-party methodology packs can extend `build-base` and override only the stages they need. for implementation, packs should keep the gas city drain lifecycle and point the two static drain steps at pack-specific item formulas that extend `do-work` and `do-work-item`. the repository currently ships concrete vendored implementations for compound engineering, superpowers, bmad method, and garrytan/gstack. those packs import this pack as `gc` internally, so users can import one methodology pack at city scope while keeping the existing `gc.*` role override surface for rig agents. third-party packs should treat upstream agent definitions, prompts, and skills as vendored methodology inputs, not as runtime authority. when an upstream methodology says to spawn subagents, dispatch a task tool, or run a plugin command, the pack should convert that shape into a gas city formula or expansion with explicit `gc.*` lanes. the model may read the upstream persona or prompt file for behavior, but work routing, retries, persistence, and fanout/fanin must remain in the gas city graph. raw-framework subagents become gas city fanouts. that is the core rule for methodology packs: preserve the user-visible process, reviewer perspective, and handoff order, but make the routing durable through beads, drains, formulas, and expansion children. structured step-file prompts are usually good candidates for separate formulas or expansion loops because gas city can then retry, observe, and resume each step independently. use two mode concepts when designing or launching methodology formulas: - `interaction_mode` controls human participation in planning and gates. `interactive` preserves blocking questions and approval menus, `autonomous` lets the workflow make reasonable decisions while recording evidence, and `headless` is for automation with no blocking prompts. - `review_mode` controls review authority. `report` is read-only output for adapters such as github pr comments, `agent` is a structured machine handoff whose caller applies fixes, and `interactive` preserves a raw top-level review experience that may apply safe fixes when the methodology allows it. the current cross-framework audit and follow-up proposal lives in [`docs/design/build-methodology-framework-audit.md`](../docs/design/build-methodology-framework-audit.md). ## requirements ledgers this base pack keeps product requirements beside the formula implementation: - `requirements.md` defines the build methodology base contract and the default `build-basic` implementation expectations. - `formulas/requirements.md` contains one durable behavior row for every base pack formula. when a base formula's stage order, selector variables, drain behavior, artifact contract, fanout/fanin, review loop, adapter side effect, or catalog surface changes, update the matching requirements row in the same change. the formula asset tests fail if a formula exists without a requirements row. ## build flow `build-base` is the virtual contract. `build-basic` is the concrete gas city implementation that demonstrates the override surface without vendoring a third-party methodology. ```mermaid flowchart td classdef base fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef basic fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch build-basic
or child of build-base\"]:::terminal --> prepare[\"prepare
build-base contract\"]:::base prepare --> requirements[\"requirements
build-basic override
gc.requirements-planner\"]:::basic requirements --> plan[\"plan
build-basic override
gc.design-author\"]:::basic plan --> planreview[\"plan-review
build-basic design review\"]:::basic planreview --> plangate{\"plan approved?
design-review contract\"}:::infra plangate -->|no: revise plan/review artifact| plan plangate -->|yes| decompose[\"decompose
build-basic task beads\"]:::basic decompose --> convoy[\"implementation convoy
bead dependency graph\"]:::infra convoy --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra subgraph itemformula[\"do-work item formulas: one formula instance per implementation bead\"] direction tb separateprep[\"prepare-worktree
do-work\"]:::base --> implement[\"implement owned work
gc.implementation-worker\"]:::basic sharedimplement[\"implement shared item
do-work-item\"]:::base implement --> closeitem[\"close source anchor
do-work\"]:::base end drainseparate --> separateprep drainshared --> sharedimplement closeitem --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra sharedimplement --> drainfanin drainfanin --> review[\"review
build-basic implementation review\"]:::basic subgraph reviewloop[\"post-implementation review loop: build-basic single-lane default\"] direction tb review --> reviewlens[\"implementation review
single reviewer lane\"]:::basic reviewlens --> reviewreport[\"synthesize findings and required fixes\"]:::basic reviewreport --> applyfixes[\"apply required fixes when needed\"]:::basic applyfixes --> reviewgate{\"implementation approved?
review contract\"}:::infra reviewgate -->|no: fix and re-review| reviewlens reviewgate -->|yes| reviewdone[\"review approved\"]:::basic end reviewdone --> finalize[\"finalize
build-basic summary\"]:::basic finalize --> publish[\"publish
build-basic or build-base no-op/push/pr\"]:::basic publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are the base contract or inherited item lifecycle, green nodes are the concrete `build-basic` implementation, and amber nodes are gas city graph, convoy, or drain infrastructure. concrete methodology packs extend the same shape: they can override requirements, planning, review fanout, item formulas, or finalization while preserving the convoy/drain/fan-in mechanics. the `build-basic` review step is intentionally single-lane; packs such as superpowers, compound engineering, and bmad replace it with expansion formulas whose reviewer beads fan out before synthesis. ## stable workflow override interface this section is the external compatibility promise for this pack's workflow customization surface. the pack exposes two stable customization modes: 1. **basic asset shadowing.** put a markdown file at the same relative path in a higher-priority city or local pack layer. formula step bodies live at `assets/workflows//.md`. gas city resolves these `description_file` assets through the normal import/layer search path, so the shadowing file replaces the base prompt text without changing the formula graph. 2. **advanced step override.** copy the formula into a higher-priority formula layer and replace the documented step block. preserve the formula name, vars, metadata keys, dependency edges, and sink contracts that downstream steps still depend on. use this when you need to replace one step with another formula, an expansion, a wider fanout, a retry loop, or a different agent route. only the override points below are intended as stable public interfaces. other steps may be useful to inspect, but they are implementation details unless they are listed here. when overriding a step that writes a downstream artifact, keep the artifact contract stable: - verdict reports must use `schema: gc.verdict-report.v1` with `verdict: pass|fail`. - github issue triage reports must use `schema: gc.github-issue-triage-report.v1`. - github adapter workflows must preserve the documented `gc.github.*` metadata on the workflow root bead. - build and implementation workflows must not close the input convoy head unless the base step explicitly does so. - steps that only validate context must not mutate source files. ### github issue triage use this when you want project-specific triage instructions for issue labels, severity, evidence requirements, or public comment style. stable basic override: `assets/workflows/github-issue-triage-base/write-triage-report.md` stable advanced step: `github-issue-triage-base` step `write-triage-report` basic example: local label and severity policy. create `assets/workflows/github-issue-triage-base/write-triage-report.md` in your city assets: ```markdown apply the repository triage policy before writing `triage-report.md`. - if the issue touches `internal/api/`, `docs/schema/openapi.*`, or generated dashboard client types, read `engdocs/architecture/api-control-plane.md` and `engdocs/contributors/huma-usage.md` before assigning priority. - mark `needs_info` when the report lacks a reproduction, failing test, stack trace, linked ci artifact, or exact version. do not invent a reproduction. - use priority `p1` only when current `origin/main` is affected or data loss is plausible. use `p2` for historical release-only regressions unless security or data integrity is involved. - include suggested github labels in the human-readable body, but keep the yaml front matter limited to the schema accepted by the validator. ``` advanced example: replace triage with a two-lane evidence pass and synthesis. copy `github-issue-triage-base.formula.toml` into your local formula layer and replace only `write-triage-report`: ```toml [[steps]] id = \"write-triage-report\" title = \"run product and engineering triage\" needs = [\"reuse-current-body-hash\"] expand = \"company-github-issue-triage-quorum\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the expansion should read the same `gc.github.snapshot_path` and write the same `triage-report.md` under `gc.github.triage_dir`. it may run separate product and engineering lanes, but its sink must still validate as `gc.github-issue-triage-report.v1` so `render-comment`, human gating, and `post-comment` continue to work. ### requirements planning use this when issue-fix requirements need local acceptance criteria, release policy, customer-impact language, or compatibility constraints. stable basic override: `assets/workflows/github-issue-fix-base/generate-requirements.md` stable advanced step: `github-issue-fix-base` step `generate-requirements` basic example: require w6h and example-mapping coverage. create `assets/workflows/github-issue-fix-base/generate-requirements.md`: ```markdown generate requirements using the local planning standard. - include who, what, when, where, why, and how sections. - add an example mapping section with at least one happy path, one negative path, and one edge case tied to the github issue evidence. - call out compatibility constraints for existing cli flags, persisted bead metadata, and public github comments. - do not approve implementation until every acceptance criterion can be tested by a unit, integration, or explicit manual verification step. ``` advanced example: route requirements through a policy gate. copy `github-issue-fix-base.formula.toml` and replace `generate-requirements`: ```toml [[steps]] id = \"generate-requirements\" title = \"generate policy-gated requirements\" needs = [\"update-status-started\"] expand = \"company-requirements-with-quality-gate\" metadata = { \"gc.run_target\" = \"gc.requirements-planner\" } ``` the replacement should still write the requirements artifact path back to the workflow root metadata key expected by later implementation-plan and create-beads steps. it may add policy review, customer-impact review, or approval lanes before closing. ### implementation plan use this when implementation plans need local architecture constraints, migration rules, rollout notes, or repository-specific boundaries. stable basic override: `assets/workflows/github-issue-fix-base/implementation-plan.md` stable advanced step: `github-issue-fix-base` step `implementation-plan` basic example: enforce architecture docs for api changes. create `assets/workflows/github-issue-fix-base/implementation-plan.md`: ```markdown before writing or updating `implementation-plan.md`, classify the affected area. - for `internal/api/`, cli api-client code, sse events, generated openapi, or dashboard generated types, read `engdocs/architecture/api-control-plane.md` and `engdocs/contributors/huma-usage.md`. - include a \"wire contract\" section explaining request/response/event type changes and generated-code impact. - include a \"migration and rollback\" section for persisted metadata, database-like state, or external github comments. - if the plan adds framework logic that could belong in prompt/config, call that out and choose the prompt/config path unless there is a clear sdk primitive requirement. ``` advanced example: replace the single-author implementation plan with architecture and test-risk lanes. copy `github-issue-fix-base.formula.toml` and replace `implementation-plan`: ```toml [[steps]] id = \"implementation-plan\" title = \"write implementation plan through architecture quorum\" needs = [\"generate-requirements\"] expand = \"company-architecture-design-quorum\" metadata = { \"gc.run_target\" = \"gc.design-author\" } ``` the expansion can fan out to architecture, operations, and test-risk authors, then synthesize one `implementation-plan.md`. the sink must still publish the implementation-plan path in the same workflow-root metadata used by `design-review` and `create-beads`. ### design review use this when implementation plans need stricter approval rules or a broader review group before bead creation begins. stable basic override: `assets/workflows/design-review/design-review.md` stable advanced steps: `design-review` step `design-review`; `github-issue-fix-base` step `design-review` basic example: require security and operability review notes. create `assets/workflows/design-review/design-review.md`: ```markdown review the implementation plan with local release-readiness expectations. - security-sensitive input, auth, github token use, filesystem writes, and shell execution require explicit threat notes. - operational changes need rollback, observability, and failure-mode notes. - implementation plans that affect persisted bead metadata must identify the metadata keys, migration behavior, and how old runs remain readable. - approve only when required changes are applied to the implementation plan, not deferred to implementation. ``` advanced example: replace the design review step with an n-wide review loop. copy `design-review.formula.toml` and replace `design-review`: ```toml [[steps]] id = \"design-review\" title = \"run architecture, security, and test plan quorum\" expand = \"company-design-review-n-wide\" metadata = { \"gc.scope_ref\" = \"body\", \"gc.scope_role\" = \"member\", \"gc.on_fail\" = \"abort_scope\", \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the replacement may create separate review artifacts, but `finalize` should still be able to determine whether the implementation plan is approved or blocked and write the terminal notification expected by the base workflow. ### create beads use this when task breakdown needs local slicing rules, dependency conventions, or bead/convoy naming standards. stable basic override: `assets/workflows/github-issue-fix-base/create-beads.md` stable advanced step: `github-issue-fix-base` step `create-beads` basic example: enforce vertical slices and dependency hygiene. create `assets/workflows/github-issue-fix-base/create-beads.md`: ```markdown create runnable implementation beads from the approved implementation plan. - prefer vertical slices that each produce a testable behavior change. - do not create \"refactor first\" tasks unless the implementation plan explicitly requires the refactor as a prerequisite for user-visible behavior. - every task must identify expected files or modules, acceptance checks, and dependencies on earlier tasks. - use nested `convoys[]` and `beads[]` in `tasks.md`; never use `epics[]`. ``` advanced example: replace bead creation with quality-gated task generation. copy `github-issue-fix-base.formula.toml` and replace `create-beads`: ```toml [[steps]] id = \"create-beads\" title = \"create quality-gated implementation convoy\" needs = [\"design-review\"] expand = \"company-create-beads-with-architect-review\" metadata = { \"gc.run_target\" = \"gc.task-decomposer\" } ``` the expansion can draft tasks, run a bead-creation quality gate, revise until approved, create the task beads and convoy, and record the created mapping. it must still produce the `tasks.md` shape consumed by `assets/scripts/create_beads_from_tasks.py` and record the created convoy metadata expected by downstream build dispatch. ### post-implementation review use this when the build needs different post-implementation review evidence, review lanes, synthesis, or fix policy. gap-analysis belongs in this review fanout instead of running as a separate lifecycle stage. stable basic override: `assets/workflows/build-basic/review.md` stable advanced steps: `build-base` step `review` basic example: tune review evidence requirements. create `assets/workflows/build-basic/review.md`: ```markdown run implementation review with local release criteria. - include the implementation summary, requirements coverage, changed files, and test commands in the review context. - treat missing migration rollback notes as blocking for persisted metadata or schema changes. - require `make test-fast-parallel` evidence when go code changed, unless the implementation summary explains why a narrower test is sufficient. - if review fails, the fix pass must address only blocking findings, not optional cleanup. ``` advanced example: replace local review with an n-wide review and synthesize loop. override the `review` stage in a concrete child of `build-base`: ```toml [[steps]] id = \"review\" title = \"run company review quorum\" needs = [\"implement\"] expand = \"company-review-n-wide\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the expansion can run several independent reviewers, synthesize required findings, run the required fix pass, and loop. include a requirements coverage lane in the quorum so gap-analysis findings are handled with the rest of review. its final output must be compatible with the base build expectation: pass means finalize may run; fail means the workflow records actionable blocking findings. ### direct implementation use this when launching implementation directly against an approved convoy without the full build loop. stable basic override: `assets/workflows/implement/prepare.md` stable advanced steps: `implement` steps `prepare`, `drain-separate`, `drain-same-session`, `wait-for-drain`, `summarize` basic example: add local preflight checks before draining work. create `assets/workflows/implement/prepare.md`: ```markdown validate the implementation launch before any worker edits source files. - confirm the input convoy contains only runnable implementation beads or nested convoys expected by the approved plan. - read `context_path` when provided and reject paths outside the rig root. - verify the working tree is not already on a protected release branch. - do not edit source files, create commits, or run implementation loops in this launcher step. ``` advanced example: force a same-session implementation policy. copy `implement.formula.toml` and replace the drain steps so only the shared lane remains: ```toml [[steps]] id = \"drain-same-session\" title = \"drain implementation in one shared session\" needs = [\"prepare\"] metadata = { \"gc.run_target\" = \"gc.implementation-worker\" } description_file = \"../assets/workflows/implement/drain-same-session.md\" [steps.drain] context = \"shared\" formula = \"do-work-item\" on_item_failure = \"skip_remaining\" member_access = \"exclusive\" [steps.drain.item] single_lane = true ``` keep `wait-for-drain` and `summarize` compatible with the base drain manifest. if your override changes the drain policy, make sure operators can still see which source anchors passed, failed, or were skipped. ### per-item implementation use this for the worker behavior applied to each drained implementation item. stable basic override: `assets/workflows/do-work/implement.md` stable advanced steps: `do-work` steps `prepare-worktree`, `implement`, `close-source-anchor`; `do-work-item` step `implement-item` basic example: require local test selection and worktree discipline. create `assets/workflows/do-work/implement.md`: ```markdown implement only the assigned source anchor. - read `work_dir` from source-anchor metadata and `cd` there before editing. - select tests based on the changed area and explain why they are sufficient. - for go changes, prefer package-level tests first, then `make test-fast-parallel` before closing if the change spans packages. - leave unrelated files and unassigned beads untouched. do not close the source anchor; the close step owns that. ``` advanced example: replace each item with a build-test-repair loop. copy `do-work.formula.toml` and replace `implement`: ```toml [[steps]] id = \"implement\" title = \"implement item through build-test-repair loop\" needs = [\"prepare-worktree\"] expand = \"company-implementation-item-loop\" metadata = { \"gc.run_target\" = \"gc.implementation-worker\" } ``` the loop can implement, test, repair, and self-review the item, but `close-source-anchor` must still be able to verify the source anchor outcome and close it with `gc.outcome=pass`. ### gap analysis use this when the implementation-vs-plan comparison needs local acceptance criteria, compliance checks, or traceability rules. stable basic override: `assets/workflows/gap-analysis/write-report.md` stable advanced step: `gap-analysis` step `write-report` basic example: enforce acceptance-criteria traceability. create `assets/workflows/gap-analysis/write-report.md`: ```markdown write the gap-analysis report with explicit traceability. - for every approved acceptance criterion, mark `met`, `partially_met`, or `missing`. - link each `met` item to changed files, tests, commits, or artifacts. - treat untested acceptance criteria as gaps unless the implementation summary explains a legitimate manual verification. - use `verdict: fail` when any required behavior, migration note, or test evidence is missing. ``` advanced example: replace report writing with independent product and test gap lanes. copy `gap-analysis.formula.toml` and replace `write-report`: ```toml [[steps]] id = \"write-report\" title = \"run product and test gap analysis\" needs = [\"validate-context\"] expand = \"company-gap-analysis-quorum\" metadata = { \"gc.run_target\" = \"gc.gap-analyst\" } ``` the expansion may produce multiple internal reports, but its final sink must write `{{report_path}}` with `schema: gc.verdict-report.v1` and `verdict: pass|fail`. ### implementation review use this when code review needs repository-specific risk checks, reviewer personas, or approval gates. stable basic override: `assets/workflows/review/write-report.md` stable advanced step: `review` step `write-report` basic example: add local review blockers. create `assets/workflows/review/write-report.md`: ```markdown write the implementation review report using local blocking criteria. - findings must include file path, line or symbol, severity, and evidence. - block on data loss risk, auth/token misuse, untyped api wires, missing rollback for persisted metadata, or tests that do not exercise the bug path. - do not block on style-only comments unless they hide a maintainability or correctness risk. - use `verdict: pass` only when no blocking findings remain. ``` advanced example: replace single review with three independent reviewers and a synthesis pass. copy `review.formula.toml` and replace `write-report`: ```toml [[steps]] id = \"write-report\" title = \"run three-lane implementation review\" needs = [\"validate-context\"] expand = \"company-implementation-review-quorum\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the replacement may use separate correctness, test-risk, and operations lanes. it must synthesize one `{{report_path}}` verdict report with only actionable blocking findings. ### github pr review use this when pr comments need local review policy, human gating rules, or different review depth before posting. stable basic override: `assets/workflows/github-pr-review/run-review.md` stable advanced steps: `github-pr-review` steps `run-review`, `human-gate-comment`, `post-comment` basic example: require maintainer-grade pr findings. create `assets/workflows/github-pr-review/run-review.md`: ```markdown review the pull request as a maintainer. - prioritize regressions, missing tests, security issues, data-loss risk, and public api breakage. - each finding must include the pr diff location, why it is a real bug, and the smallest acceptable fix. - do not post optional style suggestions as blocking findings. - if no blocking issues exist, say that clearly and mention the remaining test risk. ``` advanced example: replace pr review with multi-model review before rendering a comment. copy `github-pr-review.formula.toml` and replace `run-review`: ```toml [[steps]] id = \"run-review\" title = \"run multi-model pr review\" needs = [\"reuse-current-head\"] expand = \"company-pr-review-multi-model\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the expansion should still produce the review artifact consumed by `render-comment`. keep `post_mode=human_gate` behavior intact when configured. ### publish use this when publishing needs local branch, pr title, protected-branch, or release policy. stable basic override: `assets/workflows/publish/preflight.md` stable advanced steps: `publish` steps `preflight`, `push`, `open-pr` basic example: enforce branch and pr policy. create `assets/workflows/publish/preflight.md`: ```markdown validate publish policy before pushing or opening a pr. - never push directly to `main`, release branches, or protected environment branches. - pr titles must include the issue or convoy identifier when available. - pr bodies must include tests run, rollout risk, and rollback notes when persisted state or external comments changed. - fail closed if credentials cannot prove the remote update is lease-checked or create-if-absent. ``` advanced example: replace pr creation with a release-train handoff. copy `publish.formula.toml` and replace `open-pr`: ```toml [[steps]] id = \"open-pr\" title = \"create release-train handoff\" needs = [\"push\"] expand = \"company-release-train-handoff\" metadata = { \"gc.run_target\" = \"gc.publisher\" } ``` the replacement can open a pr, create a release-train ticket, or request human approval, but it must leave a durable final report path and terminal publish status for the caller. by default artifacts go under the target rig: ```text /plans// requirements.md implementation-plan.md tasks.md context.yaml build/final-report.md ``` the mayor may use a different artifact root when the user explicitly asks for one. if the default `plans/` directory already exists and appears unrelated to gc planning artifacts, the helper falls back to `/gc-plans`. the same `/` structure should be used under the override root. the mayor uses `assets/scripts/create_beads_from_tasks.py` after approving a task plan. the script requires python 3 with pyyaml available, invokes `gc bd --rig ` for runnable beads, invokes `gc convoy --rig ` for convoy heads and membership, and records the created mapping in `tasks.md`. the `tasks.md` payload uses nested `convoys[]` and `beads[]`; `epics[]` is a hard validation error. `convoys[].dependencies` expands to runnable bead edges from the upstream terminal runnable beads to the downstream root runnable beads. context bundles are yaml or json files with: ```yaml items: - name: requirements path: requirements.md description: product requirements and acceptance criteria. ``` each item has only `name`, `path`, and `description`. validate with: ```sh python3 /assets/scripts/validate_context_bundle.py context.yaml --allow-root ``` gap-analysis and review reports use `schema: gc.verdict-report.v1` front matter with `verdict: pass|fail`. validate with: ```sh python3 /assets/scripts/validate_verdict_report.py report.md --kind review ``` ## github adapter workflows the github workflows are targetless `graph.v2` formulas. they accept only full canonical urls: ```text https://github.com///issues/ https://github.com///pull/ ``` launch triage: ```sh gc sling gc.run-operator github-issue-triage --formula \\ --var github_issue_url=https://github.com///issues/ ``` customize triage behavior by setting `triage_rubric_path` to a markdown rubric/prompt, either at launch or in a rig's `formula_vars`. the rubric can carry project-specific label policy, priority semantics, investigation rules, or a richer report skeleton. the base formula still owns the metadata handoff, report schema, validator, security gate, and comment protocol. for deeper customization, create a local `github-issue-triage` formula that extends `github-issue-triage-base` and overrides only `write-triage-report`. that replacement step can inline an expansion or delegate to another workflow, but it should still read the pack-owned github metadata from the workflow root bead and write the same `gc.github-issue-triage-report.v1` sink metadata. launch pr review: ```sh gc sling gc.run-operator github-pr-review --formula \\ --var github_pr_url=https://github.com///pull/ \\ --var post_mode=human_gate ``` launch issue fix: ```sh gc sling gc.run-operator github-issue-fix --formula \\ --var github_issue_url=https://github.com///issues/ \\ --var mode=interactive \\ --var pr_mode=none \\ --var drain_policy=separate ``` github api calls go through wrapper scripts in `/assets/scripts/`. formulas should call those wrappers, not `gh` directly, except when diagnosing wrapper failures. 0.1.1 refresh gas city planning pack release pin. 0.1.2 mark pack test scripts executable (aligns the release hash with the extension-derived modes used by module embeds). 0.1.3 align build-basic review expansion and artifact guidance with graph.v2 validation. 0.1.4 normalize legacy work option metadata in task creation. 0.1.6 release graph.v2 review/build prompt fixes validated by gascity rc gates.", + "search_text": "gascity gascity-packs maintained gas city gas city planning and implementation workflow pack. https://github.com/gastownhall/gascity-packs/tree/main/gascity git # gas city build pack (`gc`) this is the base pack for running full software-delivery workflows in gas city: gather requirements, write and review a plan, decompose into tasks, implement in parallel agent sessions, review the result, and optionally publish. it ships three things: - **workflow formulas** — `build-basic` (the starter factory), the `build-from-*` continuation entrypoints, direct `implement`, and github adapter workflows (issue triage, issue fix, pr review). - **the `gc.mayor` coordinator skill** — a user-facing planner that gathers requirements, writes plans, creates approved beads/convoys, and launches the right formula for you. - **the `build-base` contract** — the virtual stage sequence that the methodology packs in this repository (bmad, compound-engineering, superpowers, gstack) extend and override. ## quick start: your first build prerequisites: gas city installed and a city running (`gc init`, `gc start`), and your project added as a rig (`gc rig add .` inside the repo). see the [repository readme](../readme.md) for the from-scratch path. 1. import formulas, claim command, and rig roles. from city directory: ```sh gc import add --name gc https://github.com/gastownhall/gascity-packs.git//gascity ``` then add rig-scoped roles in `city.toml` and run `gc import install`: ```toml [[rigs]] name = \"your-project\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` both imports are required. the rig-scoped roles pack supplies agents but, by design, rig imports do not register city commands; importing roles alone renders prompts that reference `gc gc claim` without installing that command. keep the top-level gas city pack imported at city scope. (contributors hacking on packs can point this source at a local clone.) 2. create a bead describing what you want built, and sling the starter factory at it: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on build-basic \\ --var artifact_root=plans/json-flag/build ``` 3. watch it run. the workflow walks requirements → plan → plan review → decompose → parallel implementation → a three-lane starter review (acceptance, test evidence, simplicity) → finalize. attach to any agent session with `gc session attach `, or inspect the workflow root bead as stages record their artifacts. 4. read the results under `artifact_root` in your rig: `requirements.md`, the implementation plan, review reports, and `factory-run.md` — a short summary of what ran, what was proven, and the suggested next action. prefer a guided experience? run the coordinator skill instead and let it do the planning and launching: ```text use skill gc.mayor ``` ## choosing an entrypoint an idea or request is the input to the software development lifecycle (sdlc). the `build-from-*` continuation formulas are named for the point where they enter it. for example, `build-from-requirements` starts at requirements and gathers requirements from the idea or request you provide. each full-lifecycle formula runs from its starting phase through a complete, tested, and reviewed implementation with any required review fixes applied, plus a final report. the result may remain local, be pushed, or be published as a pr. the sdlc phases are: - requirements - implementation planning - plan review - plan decomposition into beads - implementation and testing - implementation review/fix - finalization - optional publication (push or pr) | what you have | public formula | sdlc phase you're starting from | | --- | --- | --- | | a target bead containing an idea or request | `build-basic` | requirements | | an idea or request, but no target bead | `build-from-requirements` | requirements | | an approved requirements (aka plan) markdown file | `build-from-plan` | implementation planning | | approved requirements and a reviewed implementation plan | `build-from-decompose` | plan decomposition | | a gas city implementation convoy and its requirements, implementation plan, and decomposition | `build-from-convoy` | implementation | ### focused workflows | what you want to do | public formula | successful output or boundary | | --- | --- | --- | | harden an implementation plan | `design-review` | revised implementation plan approved or blocked by its review loop | | implement an approved convoy without the full build suffix | `implement` | implementation summary and optional publication; no normal review/fix/finalize suffix | | resume an existing gas city build at implementation review | `build-from-review` | full-lifecycle output described above; requires its upstream build artifacts and implementation evidence | | review an implementation | `review` | review report only | | check requirements and implementation coverage | `gap-analysis` | coverage report only | | assess a github issue | `github-issue-triage` | triage report and a sticky issue comment, created or updated; no implementation | | fix a github issue | `github-issue-fix` | implemented and reviewed issue fix; sticky issue-fix status comment created or updated; optional draft or ready pr | | review a github pr | `github-pr-review` | review report and a sticky pr comment for the current head, created or updated; no code changes, formal github review, or merge | the normal build continuations use one implementation-plan review gate; a required-changes or blocked verdict stops the continuation. `design-review` and `github-issue-fix` provide review loops that harden an implementation plan. testing evidence is required; tdd is not. `gc.mayor` can gather the upstream artifact paths needed by the continuation formulas. discover everything that is launchable from the active rig/city context: ```sh gc formula catalog --json gc formula show implement --json ``` launch implementation directly against an approved implementation convoy: ```sh gc sling gc.run-operator --on implement \\ --var context_path= \\ --var drain_policy=separate ``` ## launch variables that matter every build entrypoint accepts the same knobs. set them with `--var k=v` at launch or pin them in a rig's `formula_vars`: | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | (required) | directory under the rig where all build artifacts land. | | `interaction_mode` | `interactive` | `interactive` keeps blocking questions and approval menus; `autonomous` decides and records evidence; `headless` never prompts. | | `review_mode` | `agent` | `report` is read-only findings; `agent` is a structured fix handoff; `interactive` may apply safe fixes directly. | | `drain_policy` | `separate` | `separate` runs implementation beads in parallel worker sessions; `same-session` runs them serially in one shared session. | | `implementation_target` | `gc.implementation-worker` | the rig role that implements each work item. | | `push` / `open_pr` | `false` | allow the publish stage to push and open a pr after all checks pass. | | `max_iterations` | `10` | bound on implementation/review fix attempts. | when variables are not enough, every stage prompt can be replaced by dropping a markdown file at the same relative path in a higher-priority layer, and every documented step can be swapped for your own expansion — see \"stable workflow override interface\" below for the recipe and per-stage examples. if part of the factory already ran, launch the targetless continuation entrypoint that matches the artifacts you already have: ```sh gc sling gc.run-operator build-from-decompose --formula \\ --var artifact_root= \\ --var requirements_path=/requirements.md \\ --var plan_path=/implementation-plan.md \\ --var plan_review_path=/plan-review.md \\ --var drain_policy=separate ``` each continuation validates the artifacts you hand it before doing any work, records the entrypoint it started from, and refuses to finalize as a pass when review or repair is still blocked — see \"choosing an entrypoint\" above for which one matches the artifacts you have. every formula in this pack uses `contract = \"graph.v2\"`. targeted formulas take the core-injected reserved convoy target; they do not declare `issue`, `bead_id`, or `convoy_id` variables. `drain_policy=separate` is the standalone default. use `same-session` only when preserving one shared worktree and conversation is explicitly desired and core shared drain support is available. the pack ships its city-scoped claim command alongside formulas, plus providerless rig role agents under `gascity/roles`. standalone use requires the top-level `gc` import for formulas, mayor skill, and `gc gc claim`, and `gascity/roles` on each target rig for `gc.*` role agents. import roles for each target rig. by default agents inherit city/workspace provider; advanced users can patch individual roles without overriding formulas: ```toml [[rigs]] name = \"my-repo\" [rigs.imports.gc] source = \"/path/to/gascity/roles\" [[rigs.patches]] agent = \"gc.implementation-worker\" provider = \"your-provider\" ``` launch formulas from target rig context, or pass normal `--rig ` selection so `gc.run-operator` resolves to rig-local role. default formula routes use these qualified targets: `gc.run-operator`, `gc.requirements-planner`, `gc.design-author`, `gc.task-decomposer`, `gc.issue-triager`, `gc.design-implementation-reviewer`, `gc.design-test-risk-reviewer`, `gc.review-synthesizer`, `gc.implementation-worker`, `gc.gap-analyst`, `gc.implementation-reviewer`, and `gc.publisher`. ## build methodology contract `build-base` is the virtual full-lifecycle workflow contract. it defines the stable stage sequence that concrete build methodology packs can override: ```text prepare -> requirements -> plan -> plan-review -> decompose -> implement | implement-same-session -> review -> finalize -> publish ``` `build-base` is internal and should not be launched directly. use `build-basic` for the default gas city implementation. it maps the base stages onto the existing gas city requirements, implementation-plan, design-review, create-beads, implementation, post-implementation review, and publish helpers. gap-analysis is a review lens inside the post-implementation review loop, so coverage findings are synthesized and fixed with the rest of the review output. continuation bases are nested suffixes. each base validates its prerequisite inputs, performs one stage or handoff, and delegates to the next suffix: ```text build-from-requirements-base -> build-from-plan-base -> build-from-decompose-base -> build-from-convoy-base -> build-from-review-base ``` the cataloged `build-from-*` formulas are thin default gas city wrappers around those bases. methodology packs that want the same entrypoints should extend the matching `build-from-*-base` formula and override selector defaults, routes, drain item formulas, or review expansions instead of copying the suffix graph. third-party methodology packs can extend `build-base` and override only the stages they need. for implementation, packs should keep the gas city drain lifecycle and point the two static drain steps at pack-specific item formulas that extend `do-work` and `do-work-item`. the repository currently ships concrete vendored implementations for compound engineering, superpowers, bmad method, and garrytan/gstack. those packs import this pack as `gc` internally, so users can import one methodology pack at city scope while keeping the existing `gc.*` role override surface for rig agents. third-party packs should treat upstream agent definitions, prompts, and skills as vendored methodology inputs, not as runtime authority. when an upstream methodology says to spawn subagents, dispatch a task tool, or run a plugin command, the pack should convert that shape into a gas city formula or expansion with explicit `gc.*` lanes. the model may read the upstream persona or prompt file for behavior, but work routing, retries, persistence, and fanout/fanin must remain in the gas city graph. raw-framework subagents become gas city fanouts. that is the core rule for methodology packs: preserve the user-visible process, reviewer perspective, and handoff order, but make the routing durable through beads, drains, formulas, and expansion children. structured step-file prompts are usually good candidates for separate formulas or expansion loops because gas city can then retry, observe, and resume each step independently. use two mode concepts when designing or launching methodology formulas: - `interaction_mode` controls human participation in planning and gates. `interactive` preserves blocking questions and approval menus, `autonomous` lets the workflow make reasonable decisions while recording evidence, and `headless` is for automation with no blocking prompts. - `review_mode` controls review authority. `report` is read-only output for adapters such as github pr comments, `agent` is a structured machine handoff whose caller applies fixes, and `interactive` preserves a raw top-level review experience that may apply safe fixes when the methodology allows it. the current cross-framework audit and follow-up proposal lives in [`docs/design/build-methodology-framework-audit.md`](../docs/design/build-methodology-framework-audit.md). ## requirements ledgers this base pack keeps product requirements beside the formula implementation: - `requirements.md` defines the build methodology base contract and the default `build-basic` implementation expectations. - `formulas/requirements.md` contains one durable behavior row for every base pack formula. when a base formula's stage order, selector variables, drain behavior, artifact contract, fanout/fanin, review loop, adapter side effect, or catalog surface changes, update the matching requirements row in the same change. the formula asset tests fail if a formula exists without a requirements row. ## build flow `build-base` is the virtual contract. `build-basic` is the concrete gas city implementation that demonstrates the override surface without vendoring a third-party methodology. ```mermaid flowchart td classdef base fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef basic fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch build-basic
or child of build-base\"]:::terminal --> prepare[\"prepare
build-base contract\"]:::base prepare --> requirements[\"requirements
build-basic override
gc.requirements-planner\"]:::basic requirements --> plan[\"plan
build-basic override
gc.design-author\"]:::basic plan --> planreview[\"plan-review
build-basic design review\"]:::basic planreview --> plangate{\"plan approved?
design-review contract\"}:::infra plangate -->|no: revise plan/review artifact| plan plangate -->|yes| decompose[\"decompose
build-basic task beads\"]:::basic decompose --> convoy[\"implementation convoy
bead dependency graph\"]:::infra convoy --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra subgraph itemformula[\"do-work item formulas: one formula instance per implementation bead\"] direction tb separateprep[\"prepare-worktree
do-work\"]:::base --> implement[\"implement owned work
gc.implementation-worker\"]:::basic sharedimplement[\"implement shared item
do-work-item\"]:::base implement --> closeitem[\"close source anchor
do-work\"]:::base end drainseparate --> separateprep drainshared --> sharedimplement closeitem --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra sharedimplement --> drainfanin drainfanin --> review[\"review
build-basic implementation review\"]:::basic subgraph reviewloop[\"post-implementation review loop: build-basic single-lane default\"] direction tb review --> reviewlens[\"implementation review
single reviewer lane\"]:::basic reviewlens --> reviewreport[\"synthesize findings and required fixes\"]:::basic reviewreport --> applyfixes[\"apply required fixes when needed\"]:::basic applyfixes --> reviewgate{\"implementation approved?
review contract\"}:::infra reviewgate -->|no: fix and re-review| reviewlens reviewgate -->|yes| reviewdone[\"review approved\"]:::basic end reviewdone --> finalize[\"finalize
build-basic summary\"]:::basic finalize --> publish[\"publish
build-basic or build-base no-op/push/pr\"]:::basic publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are the base contract or inherited item lifecycle, green nodes are the concrete `build-basic` implementation, and amber nodes are gas city graph, convoy, or drain infrastructure. concrete methodology packs extend the same shape: they can override requirements, planning, review fanout, item formulas, or finalization while preserving the convoy/drain/fan-in mechanics. the `build-basic` review step is intentionally single-lane; packs such as superpowers, compound engineering, and bmad replace it with expansion formulas whose reviewer beads fan out before synthesis. ## stable workflow override interface this section is the external compatibility promise for this pack's workflow customization surface. the pack exposes two stable customization modes: 1. **basic asset shadowing.** put a markdown file at the same relative path in a higher-priority city or local pack layer. formula step bodies live at `assets/workflows//.md`. gas city resolves these `description_file` assets through the normal import/layer search path, so the shadowing file replaces the base prompt text without changing the formula graph. 2. **advanced step override.** copy the formula into a higher-priority formula layer and replace the documented step block. preserve the formula name, vars, metadata keys, dependency edges, and sink contracts that downstream steps still depend on. use this when you need to replace one step with another formula, an expansion, a wider fanout, a retry loop, or a different agent route. only the override points below are intended as stable public interfaces. other steps may be useful to inspect, but they are implementation details unless they are listed here. when overriding a step that writes a downstream artifact, keep the artifact contract stable: - verdict reports must use `schema: gc.verdict-report.v1` with `verdict: pass|fail`. - github issue triage reports must use `schema: gc.github-issue-triage-report.v1`. - github adapter workflows must preserve the documented `gc.github.*` metadata on the workflow root bead. - build and implementation workflows must not close the input convoy head unless the base step explicitly does so. - steps that only validate context must not mutate source files. ### github issue triage use this when you want project-specific triage instructions for issue labels, severity, evidence requirements, or public comment style. stable basic override: `assets/workflows/github-issue-triage-base/write-triage-report.md` stable advanced step: `github-issue-triage-base` step `write-triage-report` basic example: local label and severity policy. create `assets/workflows/github-issue-triage-base/write-triage-report.md` in your city assets: ```markdown apply the repository triage policy before writing `triage-report.md`. - if the issue touches `internal/api/`, `docs/schema/openapi.*`, or generated dashboard client types, read `engdocs/architecture/api-control-plane.md` and `engdocs/contributors/huma-usage.md` before assigning priority. - mark `needs_info` when the report lacks a reproduction, failing test, stack trace, linked ci artifact, or exact version. do not invent a reproduction. - use priority `p1` only when current `origin/main` is affected or data loss is plausible. use `p2` for historical release-only regressions unless security or data integrity is involved. - include suggested github labels in the human-readable body, but keep the yaml front matter limited to the schema accepted by the validator. ``` advanced example: replace triage with a two-lane evidence pass and synthesis. copy `github-issue-triage-base.formula.toml` into your local formula layer and replace only `write-triage-report`: ```toml [[steps]] id = \"write-triage-report\" title = \"run product and engineering triage\" needs = [\"reuse-current-body-hash\"] expand = \"company-github-issue-triage-quorum\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the expansion should read the same `gc.github.snapshot_path` and write the same `triage-report.md` under `gc.github.triage_dir`. it may run separate product and engineering lanes, but its sink must still validate as `gc.github-issue-triage-report.v1` so `render-comment`, human gating, and `post-comment` continue to work. ### requirements planning use this when issue-fix requirements need local acceptance criteria, release policy, customer-impact language, or compatibility constraints. stable basic override: `assets/workflows/github-issue-fix-base/generate-requirements.md` stable advanced step: `github-issue-fix-base` step `generate-requirements` basic example: require w6h and example-mapping coverage. create `assets/workflows/github-issue-fix-base/generate-requirements.md`: ```markdown generate requirements using the local planning standard. - include who, what, when, where, why, and how sections. - add an example mapping section with at least one happy path, one negative path, and one edge case tied to the github issue evidence. - call out compatibility constraints for existing cli flags, persisted bead metadata, and public github comments. - do not approve implementation until every acceptance criterion can be tested by a unit, integration, or explicit manual verification step. ``` advanced example: route requirements through a policy gate. copy `github-issue-fix-base.formula.toml` and replace `generate-requirements`: ```toml [[steps]] id = \"generate-requirements\" title = \"generate policy-gated requirements\" needs = [\"update-status-started\"] expand = \"company-requirements-with-quality-gate\" metadata = { \"gc.run_target\" = \"gc.requirements-planner\" } ``` the replacement should still write the requirements artifact path back to the workflow root metadata key expected by later implementation-plan and create-beads steps. it may add policy review, customer-impact review, or approval lanes before closing. ### implementation plan use this when implementation plans need local architecture constraints, migration rules, rollout notes, or repository-specific boundaries. stable basic override: `assets/workflows/github-issue-fix-base/implementation-plan.md` stable advanced step: `github-issue-fix-base` step `implementation-plan` basic example: enforce architecture docs for api changes. create `assets/workflows/github-issue-fix-base/implementation-plan.md`: ```markdown before writing or updating `implementation-plan.md`, classify the affected area. - for `internal/api/`, cli api-client code, sse events, generated openapi, or dashboard generated types, read `engdocs/architecture/api-control-plane.md` and `engdocs/contributors/huma-usage.md`. - include a \"wire contract\" section explaining request/response/event type changes and generated-code impact. - include a \"migration and rollback\" section for persisted metadata, database-like state, or external github comments. - if the plan adds framework logic that could belong in prompt/config, call that out and choose the prompt/config path unless there is a clear sdk primitive requirement. ``` advanced example: replace the single-author implementation plan with architecture and test-risk lanes. copy `github-issue-fix-base.formula.toml` and replace `implementation-plan`: ```toml [[steps]] id = \"implementation-plan\" title = \"write implementation plan through architecture quorum\" needs = [\"generate-requirements\"] expand = \"company-architecture-design-quorum\" metadata = { \"gc.run_target\" = \"gc.design-author\" } ``` the expansion can fan out to architecture, operations, and test-risk authors, then synthesize one `implementation-plan.md`. the sink must still publish the implementation-plan path in the same workflow-root metadata used by `design-review` and `create-beads`. ### design review use this when implementation plans need stricter approval rules or a broader review group before bead creation begins. stable basic override: `assets/workflows/design-review/design-review.md` stable advanced steps: `design-review` step `design-review`; `github-issue-fix-base` step `design-review` basic example: require security and operability review notes. create `assets/workflows/design-review/design-review.md`: ```markdown review the implementation plan with local release-readiness expectations. - security-sensitive input, auth, github token use, filesystem writes, and shell execution require explicit threat notes. - operational changes need rollback, observability, and failure-mode notes. - implementation plans that affect persisted bead metadata must identify the metadata keys, migration behavior, and how old runs remain readable. - approve only when required changes are applied to the implementation plan, not deferred to implementation. ``` advanced example: replace the design review step with an n-wide review loop. copy `design-review.formula.toml` and replace `design-review`: ```toml [[steps]] id = \"design-review\" title = \"run architecture, security, and test plan quorum\" expand = \"company-design-review-n-wide\" metadata = { \"gc.scope_ref\" = \"body\", \"gc.scope_role\" = \"member\", \"gc.on_fail\" = \"abort_scope\", \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the replacement may create separate review artifacts, but `finalize` should still be able to determine whether the implementation plan is approved or blocked and write the terminal notification expected by the base workflow. ### create beads use this when task breakdown needs local slicing rules, dependency conventions, or bead/convoy naming standards. stable basic override: `assets/workflows/github-issue-fix-base/create-beads.md` stable advanced step: `github-issue-fix-base` step `create-beads` basic example: enforce vertical slices and dependency hygiene. create `assets/workflows/github-issue-fix-base/create-beads.md`: ```markdown create runnable implementation beads from the approved implementation plan. - prefer vertical slices that each produce a testable behavior change. - do not create \"refactor first\" tasks unless the implementation plan explicitly requires the refactor as a prerequisite for user-visible behavior. - every task must identify expected files or modules, acceptance checks, and dependencies on earlier tasks. - use nested `convoys[]` and `beads[]` in `tasks.md`; never use `epics[]`. ``` advanced example: replace bead creation with quality-gated task generation. copy `github-issue-fix-base.formula.toml` and replace `create-beads`: ```toml [[steps]] id = \"create-beads\" title = \"create quality-gated implementation convoy\" needs = [\"design-review\"] expand = \"company-create-beads-with-architect-review\" metadata = { \"gc.run_target\" = \"gc.task-decomposer\" } ``` the expansion can draft tasks, run a bead-creation quality gate, revise until approved, create the task beads and convoy, and record the created mapping. it must still produce the `tasks.md` shape consumed by `assets/scripts/create_beads_from_tasks.py` and record the created convoy metadata expected by downstream build dispatch. ### post-implementation review use this when the build needs different post-implementation review evidence, review lanes, synthesis, or fix policy. gap-analysis belongs in this review fanout instead of running as a separate lifecycle stage. stable basic override: `assets/workflows/build-basic/review.md` stable advanced steps: `build-base` step `review` basic example: tune review evidence requirements. create `assets/workflows/build-basic/review.md`: ```markdown run implementation review with local release criteria. - include the implementation summary, requirements coverage, changed files, and test commands in the review context. - treat missing migration rollback notes as blocking for persisted metadata or schema changes. - require `make test-fast-parallel` evidence when go code changed, unless the implementation summary explains why a narrower test is sufficient. - if review fails, the fix pass must address only blocking findings, not optional cleanup. ``` advanced example: replace local review with an n-wide review and synthesize loop. override the `review` stage in a concrete child of `build-base`: ```toml [[steps]] id = \"review\" title = \"run company review quorum\" needs = [\"implement\"] expand = \"company-review-n-wide\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the expansion can run several independent reviewers, synthesize required findings, run the required fix pass, and loop. include a requirements coverage lane in the quorum so gap-analysis findings are handled with the rest of review. its final output must be compatible with the base build expectation: pass means finalize may run; fail means the workflow records actionable blocking findings. ### direct implementation use this when launching implementation directly against an approved convoy without the full build loop. stable basic override: `assets/workflows/implement/prepare.md` stable advanced steps: `implement` steps `prepare`, `drain-separate`, `drain-same-session`, `wait-for-drain`, `summarize` basic example: add local preflight checks before draining work. create `assets/workflows/implement/prepare.md`: ```markdown validate the implementation launch before any worker edits source files. - confirm the input convoy contains only runnable implementation beads or nested convoys expected by the approved plan. - read `context_path` when provided and reject paths outside the rig root. - verify the working tree is not already on a protected release branch. - do not edit source files, create commits, or run implementation loops in this launcher step. ``` advanced example: force a same-session implementation policy. copy `implement.formula.toml` and replace the drain steps so only the shared lane remains: ```toml [[steps]] id = \"drain-same-session\" title = \"drain implementation in one shared session\" needs = [\"prepare\"] metadata = { \"gc.run_target\" = \"gc.implementation-worker\" } description_file = \"../assets/workflows/implement/drain-same-session.md\" [steps.drain] context = \"shared\" formula = \"do-work-item\" on_item_failure = \"skip_remaining\" member_access = \"exclusive\" [steps.drain.item] single_lane = true ``` keep `wait-for-drain` and `summarize` compatible with the base drain manifest. if your override changes the drain policy, make sure operators can still see which source anchors passed, failed, or were skipped. ### per-item implementation use this for the worker behavior applied to each drained implementation item. stable basic override: `assets/workflows/do-work/implement.md` stable advanced steps: `do-work` steps `prepare-worktree`, `implement`, `close-source-anchor`; `do-work-item` step `implement-item` basic example: require local test selection and worktree discipline. create `assets/workflows/do-work/implement.md`: ```markdown implement only the assigned source anchor. - read `work_dir` from source-anchor metadata and `cd` there before editing. - select tests based on the changed area and explain why they are sufficient. - for go changes, prefer package-level tests first, then `make test-fast-parallel` before closing if the change spans packages. - leave unrelated files and unassigned beads untouched. do not close the source anchor; the close step owns that. ``` advanced example: replace each item with a build-test-repair loop. copy `do-work.formula.toml` and replace `implement`: ```toml [[steps]] id = \"implement\" title = \"implement item through build-test-repair loop\" needs = [\"prepare-worktree\"] expand = \"company-implementation-item-loop\" metadata = { \"gc.run_target\" = \"gc.implementation-worker\" } ``` the loop can implement, test, repair, and self-review the item, but `close-source-anchor` must still be able to verify the source anchor outcome and close it with `gc.outcome=pass`. ### gap analysis use this when the implementation-vs-plan comparison needs local acceptance criteria, compliance checks, or traceability rules. stable basic override: `assets/workflows/gap-analysis/write-report.md` stable advanced step: `gap-analysis` step `write-report` basic example: enforce acceptance-criteria traceability. create `assets/workflows/gap-analysis/write-report.md`: ```markdown write the gap-analysis report with explicit traceability. - for every approved acceptance criterion, mark `met`, `partially_met`, or `missing`. - link each `met` item to changed files, tests, commits, or artifacts. - treat untested acceptance criteria as gaps unless the implementation summary explains a legitimate manual verification. - use `verdict: fail` when any required behavior, migration note, or test evidence is missing. ``` advanced example: replace report writing with independent product and test gap lanes. copy `gap-analysis.formula.toml` and replace `write-report`: ```toml [[steps]] id = \"write-report\" title = \"run product and test gap analysis\" needs = [\"validate-context\"] expand = \"company-gap-analysis-quorum\" metadata = { \"gc.run_target\" = \"gc.gap-analyst\" } ``` the expansion may produce multiple internal reports, but its final sink must write `{{report_path}}` with `schema: gc.verdict-report.v1` and `verdict: pass|fail`. ### implementation review use this when code review needs repository-specific risk checks, reviewer personas, or approval gates. stable basic override: `assets/workflows/review/write-report.md` stable advanced step: `review` step `write-report` basic example: add local review blockers. create `assets/workflows/review/write-report.md`: ```markdown write the implementation review report using local blocking criteria. - findings must include file path, line or symbol, severity, and evidence. - block on data loss risk, auth/token misuse, untyped api wires, missing rollback for persisted metadata, or tests that do not exercise the bug path. - do not block on style-only comments unless they hide a maintainability or correctness risk. - use `verdict: pass` only when no blocking findings remain. ``` advanced example: replace single review with three independent reviewers and a synthesis pass. copy `review.formula.toml` and replace `write-report`: ```toml [[steps]] id = \"write-report\" title = \"run three-lane implementation review\" needs = [\"validate-context\"] expand = \"company-implementation-review-quorum\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the replacement may use separate correctness, test-risk, and operations lanes. it must synthesize one `{{report_path}}` verdict report with only actionable blocking findings. ### github pr review use this when pr comments need local review policy, human gating rules, or different review depth before posting. stable basic override: `assets/workflows/github-pr-review/run-review.md` stable advanced steps: `github-pr-review` steps `run-review`, `human-gate-comment`, `post-comment` basic example: require maintainer-grade pr findings. create `assets/workflows/github-pr-review/run-review.md`: ```markdown review the pull request as a maintainer. - prioritize regressions, missing tests, security issues, data-loss risk, and public api breakage. - each finding must include the pr diff location, why it is a real bug, and the smallest acceptable fix. - do not post optional style suggestions as blocking findings. - if no blocking issues exist, say that clearly and mention the remaining test risk. ``` advanced example: replace pr review with multi-model review before rendering a comment. copy `github-pr-review.formula.toml` and replace `run-review`: ```toml [[steps]] id = \"run-review\" title = \"run multi-model pr review\" needs = [\"reuse-current-head\"] expand = \"company-pr-review-multi-model\" metadata = { \"gc.run_target\" = \"gc.review-synthesizer\" } ``` the expansion should still produce the review artifact consumed by `render-comment`. keep `post_mode=human_gate` behavior intact when configured. ### publish use this when publishing needs local branch, pr title, protected-branch, or release policy. stable basic override: `assets/workflows/publish/preflight.md` stable advanced steps: `publish` steps `preflight`, `push`, `open-pr` basic example: enforce branch and pr policy. create `assets/workflows/publish/preflight.md`: ```markdown validate publish policy before pushing or opening a pr. - never push directly to `main`, release branches, or protected environment branches. - pr titles must include the issue or convoy identifier when available. - pr bodies must include tests run, rollout risk, and rollback notes when persisted state or external comments changed. - fail closed if credentials cannot prove the remote update is lease-checked or create-if-absent. ``` advanced example: replace pr creation with a release-train handoff. copy `publish.formula.toml` and replace `open-pr`: ```toml [[steps]] id = \"open-pr\" title = \"create release-train handoff\" needs = [\"push\"] expand = \"company-release-train-handoff\" metadata = { \"gc.run_target\" = \"gc.publisher\" } ``` the replacement can open a pr, create a release-train ticket, or request human approval, but it must leave a durable final report path and terminal publish status for the caller. by default artifacts go under the target rig: ```text /plans// requirements.md implementation-plan.md tasks.md context.yaml build/final-report.md ``` the mayor may use a different artifact root when the user explicitly asks for one. if the default `plans/` directory already exists and appears unrelated to gc planning artifacts, the helper falls back to `/gc-plans`. the same `/` structure should be used under the override root. the mayor uses `assets/scripts/create_beads_from_tasks.py` after approving a task plan. the script requires python 3 with pyyaml available, invokes `gc bd --rig ` for runnable beads, invokes `gc convoy --rig ` for convoy heads and membership, and records the created mapping in `tasks.md`. the `tasks.md` payload uses nested `convoys[]` and `beads[]`; `epics[]` is a hard validation error. `convoys[].dependencies` expands to runnable bead edges from the upstream terminal runnable beads to the downstream root runnable beads. context bundles are yaml or json files with: ```yaml items: - name: requirements path: requirements.md description: product requirements and acceptance criteria. ``` each item has only `name`, `path`, and `description`. validate with: ```sh python3 /assets/scripts/validate_context_bundle.py context.yaml --allow-root ``` gap-analysis and review reports use `schema: gc.verdict-report.v1` front matter with `verdict: pass|fail`. validate with: ```sh python3 /assets/scripts/validate_verdict_report.py report.md --kind review ``` ## github adapter workflows the github workflows are targetless `graph.v2` formulas. they accept only full canonical urls: ```text https://github.com///issues/ https://github.com///pull/ ``` launch triage: ```sh gc sling gc.run-operator github-issue-triage --formula \\ --var github_issue_url=https://github.com///issues/ ``` customize triage behavior by setting `triage_rubric_path` to a markdown rubric/prompt, either at launch or in a rig's `formula_vars`. the rubric can carry project-specific label policy, priority semantics, investigation rules, or a richer report skeleton. the base formula still owns the metadata handoff, report schema, validator, security gate, and comment protocol. for deeper customization, create a local `github-issue-triage` formula that extends `github-issue-triage-base` and overrides only `write-triage-report`. that replacement step can inline an expansion or delegate to another workflow, but it should still read the pack-owned github metadata from the workflow root bead and write the same `gc.github-issue-triage-report.v1` sink metadata. launch pr review: ```sh gc sling gc.run-operator github-pr-review --formula \\ --var github_pr_url=https://github.com///pull/ \\ --var post_mode=human_gate ``` launch issue fix: ```sh gc sling gc.run-operator github-issue-fix --formula \\ --var github_issue_url=https://github.com///issues/ \\ --var mode=interactive \\ --var pr_mode=none \\ --var drain_policy=separate ``` github api calls go through wrapper scripts in `/assets/scripts/`. formulas should call those wrappers, not `gh` directly, except when diagnosing wrapper failures. 0.1.1 refresh gas city planning pack release pin. 0.1.2 mark pack test scripts executable (aligns the release hash with the extension-derived modes used by module embeds). 0.1.3 align build-basic review expansion and artifact guidance with graph.v2 validation. 0.1.4 normalize legacy work option metadata in task creation. 0.1.6 release graph.v2 review/build prompt fixes validated by gascity rc gates.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/gascity/README.md", - "content": "# Gas City Build Pack (`gc`)\n\nThis is the base pack for running full software-delivery workflows in Gas\nCity: gather requirements, write and review a plan, decompose into tasks,\nimplement in parallel agent sessions, review the result, and optionally\npublish. It ships three things:\n\n- **Workflow formulas** — `build-basic` (the starter factory), the\n `build-from-*` continuation entrypoints, direct `implement`, and GitHub\n adapter workflows (issue triage, issue fix, PR review).\n- **The `gc.mayor` coordinator skill** — a user-facing planner that gathers\n requirements, writes plans, creates approved beads/convoys, and launches\n the right formula for you.\n- **The `build-base` contract** — the virtual stage sequence that the\n methodology packs in this repository (bmad, compound-engineering,\n superpowers, gstack) extend and override.\n\n## Quick Start: your first build\n\nPrerequisites: Gas City installed and a city running (`gc init`, `gc start`),\nand your project added as a rig (`gc rig add .` inside the repo). See the\n[repository README](../README.md) for the from-scratch path.\n\n1. Import formulas, claim command, and rig roles. From city directory:\n\n ```sh\n gc import add --name gc https://github.com/gastownhall/gascity-packs.git//gascity\n ```\n\n Then add rig-scoped roles in `city.toml` and run `gc import install`:\n\n ```toml\n [[rigs]]\n name = \"your-project\"\n\n [rigs.imports.gc]\n source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\"\n ```\n\n Both imports are required. The rig-scoped roles pack supplies agents but,\n by design, rig imports do not register city commands; importing roles alone\n renders prompts that reference `gc gc claim` without installing that\n command. Keep the top-level Gas City pack imported at city scope.\n\n (Contributors hacking on packs can point this source at a local clone.)\n\n2. Create a bead describing what you want built, and sling the starter\n factory at it:\n\n ```sh\n gc bd create \"Add a --json flag to the export command\"\n gc sling gc.run-operator --on build-basic \\\n --var artifact_root=plans/json-flag/build\n ```\n\n3. Watch it run. The workflow walks requirements → plan → plan review →\n decompose → parallel implementation → a three-lane starter review\n (acceptance, test evidence, simplicity) → finalize. Attach to any agent\n session with `gc session attach `, or inspect the workflow root\n bead as stages record their artifacts.\n\n4. Read the results under `artifact_root` in your rig: `requirements.md`,\n the implementation plan, review reports, and `factory-run.md` — a short\n summary of what ran, what was proven, and the suggested next action.\n\nPrefer a guided experience? Run the coordinator skill instead and let it do\nthe planning and launching:\n\n```text\nUse skill gc.mayor\n```\n\n## Choosing an entrypoint\n\n| You have | Launch | Notes |\n| -------- | ------ | ----- |\n| Just an idea | `build-basic` (targeted at a bead) | Full lifecycle from requirements onward. |\n| Approved requirements | `build-from-plan` | Produces plan + plan review, then continues. |\n| Approved requirements, plan, and plan review | `build-from-decompose` | Starts at decomposition. |\n| An implementation convoy | `build-from-convoy` | Drains the convoy, then reviews. |\n| Implementation evidence | `build-from-review` | Review, repair/restart handoff, finalize, publish. |\n| An approved convoy, no build wrapper wanted | `implement` | Direct drain without the review/publish suffix. |\n| A GitHub issue or PR URL | `github-issue-triage`, `github-issue-fix`, `github-pr-review` | Targetless adapters; see GitHub Adapter Workflows below. |\n\nDiscover everything that is launchable from the active rig/city context:\n\n```sh\ngc formula catalog --json\ngc formula show implement --json\n```\n\nLaunch implementation directly against an approved implementation convoy:\n\n```sh\ngc sling gc.run-operator --on implement \\\n --var context_path= \\\n --var drain_policy=separate\n```\n\n## Launch variables that matter\n\nEvery build entrypoint accepts the same knobs. Set them with `--var k=v` at\nlaunch or pin them in a rig's `formula_vars`:\n\n| Variable | Default | What it changes |\n| -------- | ------- | --------------- |\n| `artifact_root` | (required) | Directory under the rig where all build artifacts land. |\n| `interaction_mode` | `interactive` | `interactive` keeps blocking questions and approval menus; `autonomous` decides and records evidence; `headless` never prompts. |\n| `review_mode` | `agent` | `report` is read-only findings; `agent` is a structured fix handoff; `interactive` may apply safe fixes directly. |\n| `drain_policy` | `separate` | `separate` runs implementation beads in parallel worker sessions; `same-session` runs them serially in one shared session. |\n| `implementation_target` | `gc.implementation-worker` | The rig role that implements each work item. |\n| `push` / `open_pr` | `false` | Allow the publish stage to push and open a PR after all checks pass. |\n| `max_iterations` | `10` | Bound on implementation/review fix attempts. |\n\nWhen variables are not enough, every stage prompt can be replaced by dropping\na Markdown file at the same relative path in a higher-priority layer, and\nevery documented step can be swapped for your own expansion — see \"Stable\nWorkflow Override Interface\" below for the recipe and per-stage examples.\n\nIf part of the factory already ran, launch the targetless continuation\nentrypoint that matches the artifacts you already have:\n\n```sh\ngc sling gc.run-operator build-from-decompose --formula \\\n --var artifact_root= \\\n --var requirements_path=/requirements.md \\\n --var plan_path=/implementation-plan.md \\\n --var plan_review_path=/plan-review.md \\\n --var drain_policy=separate\n```\n\nEach continuation validates the artifacts you hand it before doing any work,\nrecords the entrypoint it started from, and refuses to finalize as a pass when\nreview or repair is still blocked — see \"Choosing an entrypoint\" above for\nwhich one matches the artifacts you have.\n\nEvery formula in this pack uses `contract = \"graph.v2\"`. Targeted formulas take\nthe core-injected reserved convoy target; they do not declare `issue`,\n`bead_id`, or `convoy_id` variables. `drain_policy=separate` is the standalone\ndefault. Use `same-session` only when preserving one shared worktree and\nconversation is explicitly desired and core shared drain support is available.\n\nThe pack ships its city-scoped claim command alongside formulas, plus\nproviderless rig role agents under `gascity/roles`. Standalone use requires\nthe top-level `gc` import for formulas, mayor skill, and `gc gc claim`, and\n`gascity/roles` on each target rig for `gc.*` role agents.\n\nImport roles for each target rig. By default agents inherit city/workspace\nprovider; advanced users can patch individual roles without overriding formulas:\n\n```toml\n[[rigs]]\nname = \"my-repo\"\n\n[rigs.imports.gc]\nsource = \"/path/to/gascity/roles\"\n\n[[rigs.patches]]\nagent = \"gc.implementation-worker\"\nprovider = \"your-provider\"\n```\n\nLaunch formulas from target rig context, or pass normal `--rig `\nselection so `gc.run-operator` resolves to rig-local role.\n\nDefault formula routes use these qualified targets: `gc.run-operator`,\n`gc.requirements-planner`, `gc.design-author`, `gc.task-decomposer`,\n`gc.issue-triager`, `gc.design-implementation-reviewer`,\n`gc.design-test-risk-reviewer`, `gc.review-synthesizer`,\n`gc.implementation-worker`, `gc.gap-analyst`, `gc.implementation-reviewer`,\nand `gc.publisher`.\n\n## Build Methodology Contract\n\n`build-base` is the virtual full-lifecycle workflow contract. It defines the\nstable stage sequence that concrete build methodology packs can override:\n\n```text\nprepare -> requirements -> plan -> plan-review -> decompose ->\nimplement | implement-same-session -> review -> finalize -> publish\n```\n\n`build-base` is internal and should not be launched directly. Use\n`build-basic` for the default Gas City implementation. It maps the base stages\nonto the existing Gas City requirements, implementation-plan, design-review,\ncreate-beads, implementation, post-implementation review, and publish helpers.\nGap-analysis is a review lens inside the post-implementation review loop, so\ncoverage findings are synthesized and fixed with the rest of the review output.\n\nContinuation bases are nested suffixes. Each base validates its prerequisite\ninputs, performs one stage or handoff, and delegates to the next suffix:\n\n```text\nbuild-from-requirements-base\n -> build-from-plan-base\n -> build-from-decompose-base\n -> build-from-convoy-base\n -> build-from-review-base\n```\n\nThe cataloged `build-from-*` formulas are thin default Gas City wrappers around\nthose bases. Methodology packs that want the same entrypoints should extend the\nmatching `build-from-*-base` formula and override selector defaults, routes,\ndrain item formulas, or review expansions instead of copying the suffix graph.\n\nThird-party methodology packs can extend `build-base` and override only the\nstages they need. For implementation, packs should keep the Gas City drain\nlifecycle and point the two static drain steps at pack-specific item formulas\nthat extend `do-work` and `do-work-item`. The repository currently ships\nconcrete vendored implementations for Compound Engineering, Superpowers, BMAD\nMethod, and garrytan/gstack. Those packs import this pack as `gc` internally,\nso users can import one methodology pack at city scope while keeping the\nexisting `gc.*` role override surface for rig agents.\n\nThird-party packs should treat upstream agent definitions, prompts, and skills\nas vendored methodology inputs, not as runtime authority. When an upstream\nmethodology says to spawn subagents, dispatch a task tool, or run a plugin\ncommand, the pack should convert that shape into a Gas City formula or expansion\nwith explicit `gc.*` lanes. The model may read the upstream persona or prompt\nfile for behavior, but work routing, retries, persistence, and fanout/fanin must\nremain in the Gas City graph.\n\nRaw-framework subagents become Gas City fanouts. That is the core rule for\nmethodology packs: preserve the user-visible process, reviewer perspective, and\nhandoff order, but make the routing durable through beads, drains, formulas, and\nexpansion children. Structured step-file prompts are usually good candidates for\nseparate formulas or expansion loops because Gas City can then retry, observe,\nand resume each step independently.\n\nUse two mode concepts when designing or launching methodology formulas:\n\n- `interaction_mode` controls human participation in planning and gates.\n `interactive` preserves blocking questions and approval menus, `autonomous`\n lets the workflow make reasonable decisions while recording evidence, and\n `headless` is for automation with no blocking prompts.\n- `review_mode` controls review authority. `report` is read-only output for\n adapters such as GitHub PR comments, `agent` is a structured machine handoff\n whose caller applies fixes, and `interactive` preserves a raw top-level review\n experience that may apply safe fixes when the methodology allows it.\n\nThe current cross-framework audit and follow-up proposal lives in\n[`docs/design/build-methodology-framework-audit.md`](../docs/design/build-methodology-framework-audit.md).\n\n## Requirements Ledgers\n\nThis base pack keeps product requirements beside the formula implementation:\n\n- `REQUIREMENTS.md` defines the build methodology base contract and the default\n `build-basic` implementation expectations.\n- `formulas/REQUIREMENTS.md` contains one durable behavior row for every base\n pack formula.\n\nWhen a base formula's stage order, selector variables, drain behavior, artifact\ncontract, fanout/fanin, review loop, adapter side effect, or catalog surface\nchanges, update the matching requirements row in the same change. The formula\nasset tests fail if a formula exists without a requirements row.\n\n## Build Flow\n\n`build-base` is the virtual contract. `build-basic` is the concrete Gas City\nimplementation that demonstrates the override surface without vendoring a\nthird-party methodology.\n\n```mermaid\nflowchart TD\n classDef base fill:#eef2ff,stroke:#4f46e5,color:#111827;\n classDef basic fill:#ecfdf5,stroke:#059669,color:#111827;\n classDef infra fill:#fff7ed,stroke:#d97706,color:#111827;\n classDef terminal fill:#f9fafb,stroke:#6b7280,color:#111827;\n\n Start[\"Launch build-basic
or child of build-base\"]:::terminal --> Prepare[\"prepare
build-base contract\"]:::base\n Prepare --> Requirements[\"requirements
build-basic override
gc.requirements-planner\"]:::basic\n Requirements --> Plan[\"plan
build-basic override
gc.design-author\"]:::basic\n Plan --> PlanReview[\"plan-review
build-basic design review\"]:::basic\n PlanReview --> PlanGate{\"plan approved?
design-review contract\"}:::infra\n PlanGate -->|no: revise plan/review artifact| Plan\n PlanGate -->|yes| Decompose[\"decompose
build-basic task beads\"]:::basic\n Decompose --> Convoy[\"implementation convoy
bead dependency graph\"]:::infra\n Convoy --> DrainChoice{\"drain policy
Gas City drain\"}:::infra\n DrainChoice -->|separate| DrainSeparate[\"separate drain
parallel by convoy deps\"]:::infra\n DrainChoice -->|same-session| DrainShared[\"same-session drain
single worker lane\"]:::infra\n\n subgraph ItemFormula[\"do-work item formulas: one formula instance per implementation bead\"]\n direction TB\n SeparatePrep[\"prepare-worktree
do-work\"]:::base --> Implement[\"implement owned work
gc.implementation-worker\"]:::basic\n SharedImplement[\"implement shared item
do-work-item\"]:::base\n Implement --> CloseItem[\"close source anchor
do-work\"]:::base\n end\n\n DrainSeparate --> SeparatePrep\n DrainShared --> SharedImplement\n CloseItem --> DrainFanIn[\"fan in
drain waits for all item roots\"]:::infra\n SharedImplement --> DrainFanIn\n\n DrainFanIn --> Review[\"review
build-basic implementation review\"]:::basic\n\n subgraph ReviewLoop[\"post-implementation review loop: build-basic single-lane default\"]\n direction TB\n Review --> ReviewLens[\"implementation review
single reviewer lane\"]:::basic\n ReviewLens --> ReviewReport[\"synthesize findings and required fixes\"]:::basic\n ReviewReport --> ApplyFixes[\"apply required fixes when needed\"]:::basic\n ApplyFixes --> ReviewGate{\"implementation approved?
review contract\"}:::infra\n ReviewGate -->|no: fix and re-review| ReviewLens\n ReviewGate -->|yes| ReviewDone[\"review approved\"]:::basic\n end\n\n ReviewDone --> Finalize[\"finalize
build-basic summary\"]:::basic\n Finalize --> Publish[\"publish
build-basic or build-base no-op/push/PR\"]:::basic\n Publish --> Done[\"workflow complete\"]:::terminal\n```\n\nBlue nodes are the base contract or inherited item lifecycle, green nodes are\nthe concrete `build-basic` implementation, and amber nodes are Gas City graph,\nconvoy, or drain infrastructure. Concrete methodology packs extend the same\nshape: they can override requirements, planning, review fanout, item formulas,\nor finalization while preserving the convoy/drain/fan-in mechanics. The\n`build-basic` review step is intentionally single-lane; packs such as\nSuperpowers, Compound Engineering, and BMAD replace it with expansion formulas\nwhose reviewer beads fan out before synthesis.\n\n## Stable Workflow Override Interface\n\nThis section is the external compatibility promise for this pack's workflow\ncustomization surface. The pack exposes two stable customization modes:\n\n1. **Basic asset shadowing.** Put a Markdown file at the same relative path in a\n higher-priority city or local pack layer. Formula step bodies live at\n `assets/workflows//.md`. Gas City resolves these\n `description_file` assets through the normal import/layer search path, so the\n shadowing file replaces the base prompt text without changing the formula\n graph.\n2. **Advanced step override.** Copy the formula into a higher-priority formula\n layer and replace the documented step block. Preserve the formula name, vars,\n metadata keys, dependency edges, and sink contracts that downstream steps\n still depend on. Use this when you need to replace one step with another\n formula, an expansion, a wider fanout, a retry loop, or a different agent\n route.\n\nOnly the override points below are intended as stable public interfaces. Other\nsteps may be useful to inspect, but they are implementation details unless they\nare listed here.\n\nWhen overriding a step that writes a downstream artifact, keep the artifact\ncontract stable:\n\n- Verdict reports must use `schema: gc.verdict-report.v1` with\n `verdict: pass|fail`.\n- GitHub issue triage reports must use `schema:\n gc.github-issue-triage-report.v1`.\n- GitHub adapter workflows must preserve the documented `gc.github.*` metadata\n on the workflow root bead.\n- Build and implementation workflows must not close the input convoy head unless\n the base step explicitly does so.\n- Steps that only validate context must not mutate source files.\n\n### GitHub Issue Triage\n\nUse this when you want project-specific triage instructions for issue labels,\nseverity, evidence requirements, or public comment style.\n\nStable basic override:\n`assets/workflows/github-issue-triage-base/write-triage-report.md`\n\nStable advanced step:\n`github-issue-triage-base` step `write-triage-report`\n\nBasic example: local label and severity policy.\n\nCreate `assets/workflows/github-issue-triage-base/write-triage-report.md` in\nyour city assets:\n\n```markdown\nApply the repository triage policy before writing `triage-report.md`.\n\n- If the issue touches `internal/api/`, `docs/schema/openapi.*`, or generated\n dashboard client types, read `engdocs/architecture/api-control-plane.md` and\n `engdocs/contributors/huma-usage.md` before assigning priority.\n- Mark `needs_info` when the report lacks a reproduction, failing test, stack\n trace, linked CI artifact, or exact version. Do not invent a reproduction.\n- Use priority `p1` only when current `origin/main` is affected or data loss is\n plausible. Use `p2` for historical release-only regressions unless security\n or data integrity is involved.\n- Include suggested GitHub labels in the human-readable body, but keep the YAML\n front matter limited to the schema accepted by the validator.\n```\n\nAdvanced example: replace triage with a two-lane evidence pass and synthesis.\n\nCopy `github-issue-triage-base.formula.toml` into your local formula layer and\nreplace only `write-triage-report`:\n\n```toml\n[[steps]]\nid = \"write-triage-report\"\ntitle = \"Run product and engineering triage\"\nneeds = [\"reuse-current-body-hash\"]\nexpand = \"company-github-issue-triage-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe expansion should read the same `gc.github.snapshot_path` and write the same\n`triage-report.md` under `gc.github.triage_dir`. It may run separate product and\nengineering lanes, but its sink must still validate as\n`gc.github-issue-triage-report.v1` so `render-comment`, human gating, and\n`post-comment` continue to work.\n\n### Requirements Planning\n\nUse this when issue-fix requirements need local acceptance criteria, release\npolicy, customer-impact language, or compatibility constraints.\n\nStable basic override:\n`assets/workflows/github-issue-fix-base/generate-requirements.md`\n\nStable advanced step:\n`github-issue-fix-base` step `generate-requirements`\n\nBasic example: require W6H and example-mapping coverage.\n\nCreate `assets/workflows/github-issue-fix-base/generate-requirements.md`:\n\n```markdown\nGenerate requirements using the local planning standard.\n\n- Include Who, What, When, Where, Why, and How sections.\n- Add an Example Mapping section with at least one happy path, one negative\n path, and one edge case tied to the GitHub issue evidence.\n- Call out compatibility constraints for existing CLI flags, persisted bead\n metadata, and public GitHub comments.\n- Do not approve implementation until every acceptance criterion can be tested\n by a unit, integration, or explicit manual verification step.\n```\n\nAdvanced example: route requirements through a policy gate.\n\nCopy `github-issue-fix-base.formula.toml` and replace\n`generate-requirements`:\n\n```toml\n[[steps]]\nid = \"generate-requirements\"\ntitle = \"Generate policy-gated requirements\"\nneeds = [\"update-status-started\"]\nexpand = \"company-requirements-with-quality-gate\"\nmetadata = { \"gc.run_target\" = \"gc.requirements-planner\" }\n```\n\nThe replacement should still write the requirements artifact path back to the\nworkflow root metadata key expected by later implementation-plan and\ncreate-beads steps. It may add policy review, customer-impact review, or\napproval lanes before closing.\n\n### Implementation Plan\n\nUse this when implementation plans need local architecture constraints,\nmigration rules, rollout notes, or repository-specific boundaries.\n\nStable basic override:\n`assets/workflows/github-issue-fix-base/implementation-plan.md`\n\nStable advanced step:\n`github-issue-fix-base` step `implementation-plan`\n\nBasic example: enforce architecture docs for API changes.\n\nCreate `assets/workflows/github-issue-fix-base/implementation-plan.md`:\n\n```markdown\nBefore writing or updating `implementation-plan.md`, classify the affected area.\n\n- For `internal/api/`, CLI API-client code, SSE events, generated OpenAPI, or\n dashboard generated types, read `engdocs/architecture/api-control-plane.md`\n and `engdocs/contributors/huma-usage.md`.\n- Include a \"Wire Contract\" section explaining request/response/event type\n changes and generated-code impact.\n- Include a \"Migration and Rollback\" section for persisted metadata,\n database-like state, or external GitHub comments.\n- If the plan adds framework logic that could belong in prompt/config, call\n that out and choose the prompt/config path unless there is a clear SDK\n primitive requirement.\n```\n\nAdvanced example: replace the single-author implementation plan with\narchitecture and test-risk lanes.\n\nCopy `github-issue-fix-base.formula.toml` and replace `implementation-plan`:\n\n```toml\n[[steps]]\nid = \"implementation-plan\"\ntitle = \"Write implementation plan through architecture quorum\"\nneeds = [\"generate-requirements\"]\nexpand = \"company-architecture-design-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.design-author\" }\n```\n\nThe expansion can fan out to architecture, operations, and test-risk authors,\nthen synthesize one `implementation-plan.md`. The sink must still publish the\nimplementation-plan path in the same workflow-root metadata used by\n`design-review` and `create-beads`.\n\n### Design Review\n\nUse this when implementation plans need stricter approval rules or a broader\nreview group before bead creation begins.\n\nStable basic override:\n`assets/workflows/design-review/design-review.md`\n\nStable advanced steps:\n`design-review` step `design-review`; `github-issue-fix-base` step\n`design-review`\n\nBasic example: require security and operability review notes.\n\nCreate `assets/workflows/design-review/design-review.md`:\n\n```markdown\nReview the implementation plan with local release-readiness expectations.\n\n- Security-sensitive input, auth, GitHub token use, filesystem writes, and shell\n execution require explicit threat notes.\n- Operational changes need rollback, observability, and failure-mode notes.\n- Implementation plans that affect persisted bead metadata must identify the metadata keys,\n migration behavior, and how old runs remain readable.\n- Approve only when required changes are applied to the implementation plan, not\n deferred to implementation.\n```\n\nAdvanced example: replace the design review step with an N-wide review loop.\n\nCopy `design-review.formula.toml` and replace `design-review`:\n\n```toml\n[[steps]]\nid = \"design-review\"\ntitle = \"Run architecture, security, and test plan quorum\"\nexpand = \"company-design-review-n-wide\"\nmetadata = { \"gc.scope_ref\" = \"body\", \"gc.scope_role\" = \"member\", \"gc.on_fail\" = \"abort_scope\", \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe replacement may create separate review artifacts, but `finalize` should\nstill be able to determine whether the implementation plan is approved or\nblocked and write the terminal notification expected by the base workflow.\n\n### Create Beads\n\nUse this when task breakdown needs local slicing rules, dependency conventions,\nor bead/convoy naming standards.\n\nStable basic override:\n`assets/workflows/github-issue-fix-base/create-beads.md`\n\nStable advanced step:\n`github-issue-fix-base` step `create-beads`\n\nBasic example: enforce vertical slices and dependency hygiene.\n\nCreate `assets/workflows/github-issue-fix-base/create-beads.md`:\n\n```markdown\nCreate runnable implementation beads from the approved implementation plan.\n\n- Prefer vertical slices that each produce a testable behavior change.\n- Do not create \"refactor first\" tasks unless the implementation plan\n explicitly requires the refactor as a prerequisite for user-visible behavior.\n- Every task must identify expected files or modules, acceptance checks, and\n dependencies on earlier tasks.\n- Use nested `convoys[]` and `beads[]` in `tasks.md`; never use `epics[]`.\n```\n\nAdvanced example: replace bead creation with quality-gated task generation.\n\nCopy `github-issue-fix-base.formula.toml` and replace `create-beads`:\n\n```toml\n[[steps]]\nid = \"create-beads\"\ntitle = \"Create quality-gated implementation convoy\"\nneeds = [\"design-review\"]\nexpand = \"company-create-beads-with-architect-review\"\nmetadata = { \"gc.run_target\" = \"gc.task-decomposer\" }\n```\n\nThe expansion can draft tasks, run a bead-creation quality gate, revise until\napproved, create the task beads and convoy, and record the created mapping. It\nmust still produce the `tasks.md` shape consumed by\n`assets/scripts/create_beads_from_tasks.py` and record the created convoy\nmetadata expected by downstream build dispatch.\n\n### Post-Implementation Review\n\nUse this when the build needs different post-implementation review evidence,\nreview lanes, synthesis, or fix policy. Gap-analysis belongs in this review\nfanout instead of running as a separate lifecycle stage.\n\nStable basic override:\n`assets/workflows/build-basic/review.md`\n\nStable advanced steps:\n`build-base` step `review`\n\nBasic example: tune review evidence requirements.\n\nCreate `assets/workflows/build-basic/review.md`:\n\n```markdown\nRun implementation review with local release criteria.\n\n- Include the implementation summary, requirements coverage, changed files, and\n test commands in the review context.\n- Treat missing migration rollback notes as blocking for persisted metadata or\n schema changes.\n- Require `make test-fast-parallel` evidence when Go code changed, unless the\n implementation summary explains why a narrower test is sufficient.\n- If review fails, the fix pass must address only blocking findings, not\n optional cleanup.\n```\n\nAdvanced example: replace local review with an N-wide review and synthesize\nloop.\n\nOverride the `review` stage in a concrete child of `build-base`:\n\n```toml\n[[steps]]\nid = \"review\"\ntitle = \"Run company review quorum\"\nneeds = [\"implement\"]\nexpand = \"company-review-n-wide\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe expansion can run several independent reviewers, synthesize required\nfindings, run the required fix pass, and loop. Include a requirements coverage\nlane in the quorum so gap-analysis findings are handled with the rest of review.\nIts final output must be compatible with the base build expectation: pass means\nfinalize may run; fail means the workflow records actionable blocking findings.\n\n### Direct Implementation\n\nUse this when launching implementation directly against an approved convoy\nwithout the full build loop.\n\nStable basic override:\n`assets/workflows/implement/prepare.md`\n\nStable advanced steps:\n`implement` steps `prepare`, `drain-separate`, `drain-same-session`,\n`wait-for-drain`, `summarize`\n\nBasic example: add local preflight checks before draining work.\n\nCreate `assets/workflows/implement/prepare.md`:\n\n```markdown\nValidate the implementation launch before any worker edits source files.\n\n- Confirm the input convoy contains only runnable implementation beads or nested\n convoys expected by the approved plan.\n- Read `context_path` when provided and reject paths outside the rig root.\n- Verify the working tree is not already on a protected release branch.\n- Do not edit source files, create commits, or run implementation loops in this\n launcher step.\n```\n\nAdvanced example: force a same-session implementation policy.\n\nCopy `implement.formula.toml` and replace the drain steps so only the shared\nlane remains:\n\n```toml\n[[steps]]\nid = \"drain-same-session\"\ntitle = \"Drain implementation in one shared session\"\nneeds = [\"prepare\"]\nmetadata = { \"gc.run_target\" = \"gc.implementation-worker\" }\ndescription_file = \"../assets/workflows/implement/drain-same-session.md\"\n\n[steps.drain]\ncontext = \"shared\"\nformula = \"do-work-item\"\non_item_failure = \"skip_remaining\"\nmember_access = \"exclusive\"\n\n[steps.drain.item]\nsingle_lane = true\n```\n\nKeep `wait-for-drain` and `summarize` compatible with the base drain manifest.\nIf your override changes the drain policy, make sure operators can still see\nwhich source anchors passed, failed, or were skipped.\n\n### Per-Item Implementation\n\nUse this for the worker behavior applied to each drained implementation item.\n\nStable basic override:\n`assets/workflows/do-work/implement.md`\n\nStable advanced steps:\n`do-work` steps `prepare-worktree`, `implement`, `close-source-anchor`;\n`do-work-item` step `implement-item`\n\nBasic example: require local test selection and worktree discipline.\n\nCreate `assets/workflows/do-work/implement.md`:\n\n```markdown\nImplement only the assigned source anchor.\n\n- Read `work_dir` from source-anchor metadata and `cd` there before editing.\n- Select tests based on the changed area and explain why they are sufficient.\n- For Go changes, prefer package-level tests first, then `make test-fast-parallel`\n before closing if the change spans packages.\n- Leave unrelated files and unassigned beads untouched. Do not close the source\n anchor; the close step owns that.\n```\n\nAdvanced example: replace each item with a build-test-repair loop.\n\nCopy `do-work.formula.toml` and replace `implement`:\n\n```toml\n[[steps]]\nid = \"implement\"\ntitle = \"Implement item through build-test-repair loop\"\nneeds = [\"prepare-worktree\"]\nexpand = \"company-implementation-item-loop\"\nmetadata = { \"gc.run_target\" = \"gc.implementation-worker\" }\n```\n\nThe loop can implement, test, repair, and self-review the item, but\n`close-source-anchor` must still be able to verify the source anchor outcome and\nclose it with `gc.outcome=pass`.\n\n### Gap Analysis\n\nUse this when the implementation-vs-plan comparison needs local acceptance\ncriteria, compliance checks, or traceability rules.\n\nStable basic override:\n`assets/workflows/gap-analysis/write-report.md`\n\nStable advanced step:\n`gap-analysis` step `write-report`\n\nBasic example: enforce acceptance-criteria traceability.\n\nCreate `assets/workflows/gap-analysis/write-report.md`:\n\n```markdown\nWrite the gap-analysis report with explicit traceability.\n\n- For every approved acceptance criterion, mark `met`, `partially_met`, or\n `missing`.\n- Link each `met` item to changed files, tests, commits, or artifacts.\n- Treat untested acceptance criteria as gaps unless the implementation summary\n explains a legitimate manual verification.\n- Use `verdict: fail` when any required behavior, migration note, or test\n evidence is missing.\n```\n\nAdvanced example: replace report writing with independent product and test gap\nlanes.\n\nCopy `gap-analysis.formula.toml` and replace `write-report`:\n\n```toml\n[[steps]]\nid = \"write-report\"\ntitle = \"Run product and test gap analysis\"\nneeds = [\"validate-context\"]\nexpand = \"company-gap-analysis-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.gap-analyst\" }\n```\n\nThe expansion may produce multiple internal reports, but its final sink must\nwrite `{{report_path}}` with `schema: gc.verdict-report.v1` and\n`verdict: pass|fail`.\n\n### Implementation Review\n\nUse this when code review needs repository-specific risk checks, reviewer\npersonas, or approval gates.\n\nStable basic override:\n`assets/workflows/review/write-report.md`\n\nStable advanced step:\n`review` step `write-report`\n\nBasic example: add local review blockers.\n\nCreate `assets/workflows/review/write-report.md`:\n\n```markdown\nWrite the implementation review report using local blocking criteria.\n\n- Findings must include file path, line or symbol, severity, and evidence.\n- Block on data loss risk, auth/token misuse, untyped API wires, missing\n rollback for persisted metadata, or tests that do not exercise the bug path.\n- Do not block on style-only comments unless they hide a maintainability or\n correctness risk.\n- Use `verdict: pass` only when no blocking findings remain.\n```\n\nAdvanced example: replace single review with three independent reviewers and a\nsynthesis pass.\n\nCopy `review.formula.toml` and replace `write-report`:\n\n```toml\n[[steps]]\nid = \"write-report\"\ntitle = \"Run three-lane implementation review\"\nneeds = [\"validate-context\"]\nexpand = \"company-implementation-review-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe replacement may use separate correctness, test-risk, and operations lanes.\nIt must synthesize one `{{report_path}}` verdict report with only actionable\nblocking findings.\n\n### GitHub PR Review\n\nUse this when PR comments need local review policy, human gating rules, or\ndifferent review depth before posting.\n\nStable basic override:\n`assets/workflows/github-pr-review/run-review.md`\n\nStable advanced steps:\n`github-pr-review` steps `run-review`, `human-gate-comment`, `post-comment`\n\nBasic example: require maintainer-grade PR findings.\n\nCreate `assets/workflows/github-pr-review/run-review.md`:\n\n```markdown\nReview the pull request as a maintainer.\n\n- Prioritize regressions, missing tests, security issues, data-loss risk, and\n public API breakage.\n- Each finding must include the PR diff location, why it is a real bug, and the\n smallest acceptable fix.\n- Do not post optional style suggestions as blocking findings.\n- If no blocking issues exist, say that clearly and mention the remaining test\n risk.\n```\n\nAdvanced example: replace PR review with multi-model review before rendering a\ncomment.\n\nCopy `github-pr-review.formula.toml` and replace `run-review`:\n\n```toml\n[[steps]]\nid = \"run-review\"\ntitle = \"Run multi-model PR review\"\nneeds = [\"reuse-current-head\"]\nexpand = \"company-pr-review-multi-model\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe expansion should still produce the review artifact consumed by\n`render-comment`. Keep `post_mode=human_gate` behavior intact when configured.\n\n### Publish\n\nUse this when publishing needs local branch, PR title, protected-branch, or\nrelease policy.\n\nStable basic override:\n`assets/workflows/publish/preflight.md`\n\nStable advanced steps:\n`publish` steps `preflight`, `push`, `open-pr`\n\nBasic example: enforce branch and PR policy.\n\nCreate `assets/workflows/publish/preflight.md`:\n\n```markdown\nValidate publish policy before pushing or opening a PR.\n\n- Never push directly to `main`, release branches, or protected environment\n branches.\n- PR titles must include the issue or convoy identifier when available.\n- PR bodies must include tests run, rollout risk, and rollback notes when\n persisted state or external comments changed.\n- Fail closed if credentials cannot prove the remote update is lease-checked or\n create-if-absent.\n```\n\nAdvanced example: replace PR creation with a release-train handoff.\n\nCopy `publish.formula.toml` and replace `open-pr`:\n\n```toml\n[[steps]]\nid = \"open-pr\"\ntitle = \"Create release-train handoff\"\nneeds = [\"push\"]\nexpand = \"company-release-train-handoff\"\nmetadata = { \"gc.run_target\" = \"gc.publisher\" }\n```\n\nThe replacement can open a PR, create a release-train ticket, or request human\napproval, but it must leave a durable final report path and terminal publish\nstatus for the caller.\n\nBy default artifacts go under the target rig:\n\n```text\n/plans//\n requirements.md\n implementation-plan.md\n tasks.md\n context.yaml\n build/final-report.md\n```\n\nThe mayor may use a different artifact root when the user explicitly asks for\none. If the default `plans/` directory already exists and appears unrelated to\nGC planning artifacts, the helper falls back to `/gc-plans`. The same\n`/` structure should be used under the override root.\n\nThe mayor uses `assets/scripts/create_beads_from_tasks.py` after approving a\ntask plan. The script requires Python 3 with PyYAML available, invokes `gc bd --rig\n` for runnable beads, invokes `gc convoy --rig ` for\nconvoy heads and membership, and records the created mapping in `tasks.md`.\n\nThe `tasks.md` payload uses nested `convoys[]` and `beads[]`; `epics[]` is a\nhard validation error. `convoys[].dependencies` expands to runnable bead edges\nfrom the upstream terminal runnable beads to the downstream root runnable beads.\n\nContext bundles are YAML or JSON files with:\n\n```yaml\nitems:\n - name: Requirements\n path: requirements.md\n description: Product requirements and acceptance criteria.\n```\n\nEach item has only `name`, `path`, and `description`. Validate with:\n\n```sh\npython3 /assets/scripts/validate_context_bundle.py context.yaml --allow-root \n```\n\nGap-analysis and review reports use `schema: gc.verdict-report.v1` front matter\nwith `verdict: pass|fail`. Validate with:\n\n```sh\npython3 /assets/scripts/validate_verdict_report.py report.md --kind review\n```\n\n## GitHub Adapter Workflows\n\nThe GitHub workflows are targetless `graph.v2` formulas. They accept only full\ncanonical URLs:\n\n```text\nhttps://github.com///issues/\nhttps://github.com///pull/\n```\n\nLaunch triage:\n\n```sh\ngc sling gc.run-operator github-issue-triage --formula \\\n --var github_issue_url=https://github.com///issues/\n```\n\nCustomize triage behavior by setting `triage_rubric_path` to a Markdown\nrubric/prompt, either at launch or in a rig's `formula_vars`. The rubric can\ncarry project-specific label policy, priority semantics, investigation rules,\nor a richer report skeleton. The base formula still owns the metadata handoff,\nreport schema, validator, security gate, and comment protocol.\n\nFor deeper customization, create a local `github-issue-triage` formula that\nextends `github-issue-triage-base` and overrides only `write-triage-report`.\nThat replacement step can inline an expansion or delegate to another workflow,\nbut it should still read the pack-owned GitHub metadata from the workflow root\nbead and write the same `gc.github-issue-triage-report.v1` sink metadata.\n\nLaunch PR review:\n\n```sh\ngc sling gc.run-operator github-pr-review --formula \\\n --var github_pr_url=https://github.com///pull/ \\\n --var post_mode=human_gate\n```\n\nLaunch issue fix:\n\n```sh\ngc sling gc.run-operator github-issue-fix --formula \\\n --var github_issue_url=https://github.com///issues/ \\\n --var mode=interactive \\\n --var pr_mode=none \\\n --var drain_policy=separate\n```\n\nGitHub API calls go through wrapper scripts in\n`/assets/scripts/`. Formulas should call those wrappers, not `gh`\ndirectly, except when diagnosing wrapper failures." + "content": "# Gas City Build Pack (`gc`)\n\nThis is the base pack for running full software-delivery workflows in Gas\nCity: gather requirements, write and review a plan, decompose into tasks,\nimplement in parallel agent sessions, review the result, and optionally\npublish. It ships three things:\n\n- **Workflow formulas** — `build-basic` (the starter factory), the\n `build-from-*` continuation entrypoints, direct `implement`, and GitHub\n adapter workflows (issue triage, issue fix, PR review).\n- **The `gc.mayor` coordinator skill** — a user-facing planner that gathers\n requirements, writes plans, creates approved beads/convoys, and launches\n the right formula for you.\n- **The `build-base` contract** — the virtual stage sequence that the\n methodology packs in this repository (bmad, compound-engineering,\n superpowers, gstack) extend and override.\n\n## Quick Start: your first build\n\nPrerequisites: Gas City installed and a city running (`gc init`, `gc start`),\nand your project added as a rig (`gc rig add .` inside the repo). See the\n[repository README](../README.md) for the from-scratch path.\n\n1. Import formulas, claim command, and rig roles. From city directory:\n\n ```sh\n gc import add --name gc https://github.com/gastownhall/gascity-packs.git//gascity\n ```\n\n Then add rig-scoped roles in `city.toml` and run `gc import install`:\n\n ```toml\n [[rigs]]\n name = \"your-project\"\n\n [rigs.imports.gc]\n source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\"\n ```\n\n Both imports are required. The rig-scoped roles pack supplies agents but,\n by design, rig imports do not register city commands; importing roles alone\n renders prompts that reference `gc gc claim` without installing that\n command. Keep the top-level Gas City pack imported at city scope.\n\n (Contributors hacking on packs can point this source at a local clone.)\n\n2. Create a bead describing what you want built, and sling the starter\n factory at it:\n\n ```sh\n gc bd create \"Add a --json flag to the export command\"\n gc sling gc.run-operator --on build-basic \\\n --var artifact_root=plans/json-flag/build\n ```\n\n3. Watch it run. The workflow walks requirements → plan → plan review →\n decompose → parallel implementation → a three-lane starter review\n (acceptance, test evidence, simplicity) → finalize. Attach to any agent\n session with `gc session attach `, or inspect the workflow root\n bead as stages record their artifacts.\n\n4. Read the results under `artifact_root` in your rig: `requirements.md`,\n the implementation plan, review reports, and `factory-run.md` — a short\n summary of what ran, what was proven, and the suggested next action.\n\nPrefer a guided experience? Run the coordinator skill instead and let it do\nthe planning and launching:\n\n```text\nUse skill gc.mayor\n```\n\n## Choosing an entrypoint\n\nAn idea or request is the input to the Software Development Lifecycle (SDLC).\nThe `build-from-*` continuation formulas are named for the point where they\nenter it. For example, `build-from-requirements` starts at Requirements and\ngathers requirements from the idea or request you provide.\n\nEach full-lifecycle formula runs from its starting phase through a complete,\ntested, and reviewed implementation with any required review fixes applied,\nplus a final report. The result may remain local, be pushed, or be published\nas a PR.\n\nThe SDLC phases are:\n\n- Requirements\n- Implementation Planning\n- Plan Review\n- Plan Decomposition into beads\n- Implementation and Testing\n- Implementation Review/Fix\n- Finalization\n- Optional Publication (push or PR)\n\n| What you have | Public formula | SDLC phase you're starting from |\n| --- | --- | --- |\n| A target bead containing an idea or request | `build-basic` | Requirements |\n| An idea or request, but no target bead | `build-from-requirements` | Requirements |\n| An approved requirements (aka plan) Markdown file | `build-from-plan` | Implementation Planning |\n| Approved requirements and a reviewed implementation plan | `build-from-decompose` | Plan Decomposition |\n| A Gas City implementation convoy and its requirements, implementation plan, and decomposition | `build-from-convoy` | Implementation |\n\n### Focused workflows\n\n| What you want to do | Public formula | Successful output or boundary |\n| --- | --- | --- |\n| Harden an implementation plan | `design-review` | Revised implementation plan approved or blocked by its review loop |\n| Implement an approved convoy without the full build suffix | `implement` | Implementation summary and optional publication; no normal review/fix/finalize suffix |\n| Resume an existing Gas City build at implementation review | `build-from-review` | Full-lifecycle output described above; requires its upstream build artifacts and implementation evidence |\n| Review an implementation | `review` | Review report only |\n| Check requirements and implementation coverage | `gap-analysis` | Coverage report only |\n| Assess a GitHub issue | `github-issue-triage` | Triage report and a sticky issue comment, created or updated; no implementation |\n| Fix a GitHub issue | `github-issue-fix` | Implemented and reviewed issue fix; sticky issue-fix status comment created or updated; optional draft or ready PR |\n| Review a GitHub PR | `github-pr-review` | Review report and a sticky PR comment for the current head, created or updated; no code changes, formal GitHub review, or merge |\n\nThe normal build continuations use one implementation-plan review gate; a required-changes or blocked verdict stops the continuation. `design-review` and `github-issue-fix` provide review loops that harden an implementation plan. Testing evidence is required; TDD is not. `gc.mayor` can gather the upstream artifact paths needed by the continuation formulas.\n\nDiscover everything that is launchable from the active rig/city context:\n\n```sh\ngc formula catalog --json\ngc formula show implement --json\n```\n\nLaunch implementation directly against an approved implementation convoy:\n\n```sh\ngc sling gc.run-operator --on implement \\\n --var context_path= \\\n --var drain_policy=separate\n```\n\n## Launch variables that matter\n\nEvery build entrypoint accepts the same knobs. Set them with `--var k=v` at\nlaunch or pin them in a rig's `formula_vars`:\n\n| Variable | Default | What it changes |\n| -------- | ------- | --------------- |\n| `artifact_root` | (required) | Directory under the rig where all build artifacts land. |\n| `interaction_mode` | `interactive` | `interactive` keeps blocking questions and approval menus; `autonomous` decides and records evidence; `headless` never prompts. |\n| `review_mode` | `agent` | `report` is read-only findings; `agent` is a structured fix handoff; `interactive` may apply safe fixes directly. |\n| `drain_policy` | `separate` | `separate` runs implementation beads in parallel worker sessions; `same-session` runs them serially in one shared session. |\n| `implementation_target` | `gc.implementation-worker` | The rig role that implements each work item. |\n| `push` / `open_pr` | `false` | Allow the publish stage to push and open a PR after all checks pass. |\n| `max_iterations` | `10` | Bound on implementation/review fix attempts. |\n\nWhen variables are not enough, every stage prompt can be replaced by dropping\na Markdown file at the same relative path in a higher-priority layer, and\nevery documented step can be swapped for your own expansion — see \"Stable\nWorkflow Override Interface\" below for the recipe and per-stage examples.\n\nIf part of the factory already ran, launch the targetless continuation\nentrypoint that matches the artifacts you already have:\n\n```sh\ngc sling gc.run-operator build-from-decompose --formula \\\n --var artifact_root= \\\n --var requirements_path=/requirements.md \\\n --var plan_path=/implementation-plan.md \\\n --var plan_review_path=/plan-review.md \\\n --var drain_policy=separate\n```\n\nEach continuation validates the artifacts you hand it before doing any work,\nrecords the entrypoint it started from, and refuses to finalize as a pass when\nreview or repair is still blocked — see \"Choosing an entrypoint\" above for\nwhich one matches the artifacts you have.\n\nEvery formula in this pack uses `contract = \"graph.v2\"`. Targeted formulas take\nthe core-injected reserved convoy target; they do not declare `issue`,\n`bead_id`, or `convoy_id` variables. `drain_policy=separate` is the standalone\ndefault. Use `same-session` only when preserving one shared worktree and\nconversation is explicitly desired and core shared drain support is available.\n\nThe pack ships its city-scoped claim command alongside formulas, plus\nproviderless rig role agents under `gascity/roles`. Standalone use requires\nthe top-level `gc` import for formulas, mayor skill, and `gc gc claim`, and\n`gascity/roles` on each target rig for `gc.*` role agents.\n\nImport roles for each target rig. By default agents inherit city/workspace\nprovider; advanced users can patch individual roles without overriding formulas:\n\n```toml\n[[rigs]]\nname = \"my-repo\"\n\n[rigs.imports.gc]\nsource = \"/path/to/gascity/roles\"\n\n[[rigs.patches]]\nagent = \"gc.implementation-worker\"\nprovider = \"your-provider\"\n```\n\nLaunch formulas from target rig context, or pass normal `--rig `\nselection so `gc.run-operator` resolves to rig-local role.\n\nDefault formula routes use these qualified targets: `gc.run-operator`,\n`gc.requirements-planner`, `gc.design-author`, `gc.task-decomposer`,\n`gc.issue-triager`, `gc.design-implementation-reviewer`,\n`gc.design-test-risk-reviewer`, `gc.review-synthesizer`,\n`gc.implementation-worker`, `gc.gap-analyst`, `gc.implementation-reviewer`,\nand `gc.publisher`.\n\n## Build Methodology Contract\n\n`build-base` is the virtual full-lifecycle workflow contract. It defines the\nstable stage sequence that concrete build methodology packs can override:\n\n```text\nprepare -> requirements -> plan -> plan-review -> decompose ->\nimplement | implement-same-session -> review -> finalize -> publish\n```\n\n`build-base` is internal and should not be launched directly. Use\n`build-basic` for the default Gas City implementation. It maps the base stages\nonto the existing Gas City requirements, implementation-plan, design-review,\ncreate-beads, implementation, post-implementation review, and publish helpers.\nGap-analysis is a review lens inside the post-implementation review loop, so\ncoverage findings are synthesized and fixed with the rest of the review output.\n\nContinuation bases are nested suffixes. Each base validates its prerequisite\ninputs, performs one stage or handoff, and delegates to the next suffix:\n\n```text\nbuild-from-requirements-base\n -> build-from-plan-base\n -> build-from-decompose-base\n -> build-from-convoy-base\n -> build-from-review-base\n```\n\nThe cataloged `build-from-*` formulas are thin default Gas City wrappers around\nthose bases. Methodology packs that want the same entrypoints should extend the\nmatching `build-from-*-base` formula and override selector defaults, routes,\ndrain item formulas, or review expansions instead of copying the suffix graph.\n\nThird-party methodology packs can extend `build-base` and override only the\nstages they need. For implementation, packs should keep the Gas City drain\nlifecycle and point the two static drain steps at pack-specific item formulas\nthat extend `do-work` and `do-work-item`. The repository currently ships\nconcrete vendored implementations for Compound Engineering, Superpowers, BMAD\nMethod, and garrytan/gstack. Those packs import this pack as `gc` internally,\nso users can import one methodology pack at city scope while keeping the\nexisting `gc.*` role override surface for rig agents.\n\nThird-party packs should treat upstream agent definitions, prompts, and skills\nas vendored methodology inputs, not as runtime authority. When an upstream\nmethodology says to spawn subagents, dispatch a task tool, or run a plugin\ncommand, the pack should convert that shape into a Gas City formula or expansion\nwith explicit `gc.*` lanes. The model may read the upstream persona or prompt\nfile for behavior, but work routing, retries, persistence, and fanout/fanin must\nremain in the Gas City graph.\n\nRaw-framework subagents become Gas City fanouts. That is the core rule for\nmethodology packs: preserve the user-visible process, reviewer perspective, and\nhandoff order, but make the routing durable through beads, drains, formulas, and\nexpansion children. Structured step-file prompts are usually good candidates for\nseparate formulas or expansion loops because Gas City can then retry, observe,\nand resume each step independently.\n\nUse two mode concepts when designing or launching methodology formulas:\n\n- `interaction_mode` controls human participation in planning and gates.\n `interactive` preserves blocking questions and approval menus, `autonomous`\n lets the workflow make reasonable decisions while recording evidence, and\n `headless` is for automation with no blocking prompts.\n- `review_mode` controls review authority. `report` is read-only output for\n adapters such as GitHub PR comments, `agent` is a structured machine handoff\n whose caller applies fixes, and `interactive` preserves a raw top-level review\n experience that may apply safe fixes when the methodology allows it.\n\nThe current cross-framework audit and follow-up proposal lives in\n[`docs/design/build-methodology-framework-audit.md`](../docs/design/build-methodology-framework-audit.md).\n\n## Requirements Ledgers\n\nThis base pack keeps product requirements beside the formula implementation:\n\n- `REQUIREMENTS.md` defines the build methodology base contract and the default\n `build-basic` implementation expectations.\n- `formulas/REQUIREMENTS.md` contains one durable behavior row for every base\n pack formula.\n\nWhen a base formula's stage order, selector variables, drain behavior, artifact\ncontract, fanout/fanin, review loop, adapter side effect, or catalog surface\nchanges, update the matching requirements row in the same change. The formula\nasset tests fail if a formula exists without a requirements row.\n\n## Build Flow\n\n`build-base` is the virtual contract. `build-basic` is the concrete Gas City\nimplementation that demonstrates the override surface without vendoring a\nthird-party methodology.\n\n```mermaid\nflowchart TD\n classDef base fill:#eef2ff,stroke:#4f46e5,color:#111827;\n classDef basic fill:#ecfdf5,stroke:#059669,color:#111827;\n classDef infra fill:#fff7ed,stroke:#d97706,color:#111827;\n classDef terminal fill:#f9fafb,stroke:#6b7280,color:#111827;\n\n Start[\"Launch build-basic
or child of build-base\"]:::terminal --> Prepare[\"prepare
build-base contract\"]:::base\n Prepare --> Requirements[\"requirements
build-basic override
gc.requirements-planner\"]:::basic\n Requirements --> Plan[\"plan
build-basic override
gc.design-author\"]:::basic\n Plan --> PlanReview[\"plan-review
build-basic design review\"]:::basic\n PlanReview --> PlanGate{\"plan approved?
design-review contract\"}:::infra\n PlanGate -->|no: revise plan/review artifact| Plan\n PlanGate -->|yes| Decompose[\"decompose
build-basic task beads\"]:::basic\n Decompose --> Convoy[\"implementation convoy
bead dependency graph\"]:::infra\n Convoy --> DrainChoice{\"drain policy
Gas City drain\"}:::infra\n DrainChoice -->|separate| DrainSeparate[\"separate drain
parallel by convoy deps\"]:::infra\n DrainChoice -->|same-session| DrainShared[\"same-session drain
single worker lane\"]:::infra\n\n subgraph ItemFormula[\"do-work item formulas: one formula instance per implementation bead\"]\n direction TB\n SeparatePrep[\"prepare-worktree
do-work\"]:::base --> Implement[\"implement owned work
gc.implementation-worker\"]:::basic\n SharedImplement[\"implement shared item
do-work-item\"]:::base\n Implement --> CloseItem[\"close source anchor
do-work\"]:::base\n end\n\n DrainSeparate --> SeparatePrep\n DrainShared --> SharedImplement\n CloseItem --> DrainFanIn[\"fan in
drain waits for all item roots\"]:::infra\n SharedImplement --> DrainFanIn\n\n DrainFanIn --> Review[\"review
build-basic implementation review\"]:::basic\n\n subgraph ReviewLoop[\"post-implementation review loop: build-basic single-lane default\"]\n direction TB\n Review --> ReviewLens[\"implementation review
single reviewer lane\"]:::basic\n ReviewLens --> ReviewReport[\"synthesize findings and required fixes\"]:::basic\n ReviewReport --> ApplyFixes[\"apply required fixes when needed\"]:::basic\n ApplyFixes --> ReviewGate{\"implementation approved?
review contract\"}:::infra\n ReviewGate -->|no: fix and re-review| ReviewLens\n ReviewGate -->|yes| ReviewDone[\"review approved\"]:::basic\n end\n\n ReviewDone --> Finalize[\"finalize
build-basic summary\"]:::basic\n Finalize --> Publish[\"publish
build-basic or build-base no-op/push/PR\"]:::basic\n Publish --> Done[\"workflow complete\"]:::terminal\n```\n\nBlue nodes are the base contract or inherited item lifecycle, green nodes are\nthe concrete `build-basic` implementation, and amber nodes are Gas City graph,\nconvoy, or drain infrastructure. Concrete methodology packs extend the same\nshape: they can override requirements, planning, review fanout, item formulas,\nor finalization while preserving the convoy/drain/fan-in mechanics. The\n`build-basic` review step is intentionally single-lane; packs such as\nSuperpowers, Compound Engineering, and BMAD replace it with expansion formulas\nwhose reviewer beads fan out before synthesis.\n\n## Stable Workflow Override Interface\n\nThis section is the external compatibility promise for this pack's workflow\ncustomization surface. The pack exposes two stable customization modes:\n\n1. **Basic asset shadowing.** Put a Markdown file at the same relative path in a\n higher-priority city or local pack layer. Formula step bodies live at\n `assets/workflows//.md`. Gas City resolves these\n `description_file` assets through the normal import/layer search path, so the\n shadowing file replaces the base prompt text without changing the formula\n graph.\n2. **Advanced step override.** Copy the formula into a higher-priority formula\n layer and replace the documented step block. Preserve the formula name, vars,\n metadata keys, dependency edges, and sink contracts that downstream steps\n still depend on. Use this when you need to replace one step with another\n formula, an expansion, a wider fanout, a retry loop, or a different agent\n route.\n\nOnly the override points below are intended as stable public interfaces. Other\nsteps may be useful to inspect, but they are implementation details unless they\nare listed here.\n\nWhen overriding a step that writes a downstream artifact, keep the artifact\ncontract stable:\n\n- Verdict reports must use `schema: gc.verdict-report.v1` with\n `verdict: pass|fail`.\n- GitHub issue triage reports must use `schema:\n gc.github-issue-triage-report.v1`.\n- GitHub adapter workflows must preserve the documented `gc.github.*` metadata\n on the workflow root bead.\n- Build and implementation workflows must not close the input convoy head unless\n the base step explicitly does so.\n- Steps that only validate context must not mutate source files.\n\n### GitHub Issue Triage\n\nUse this when you want project-specific triage instructions for issue labels,\nseverity, evidence requirements, or public comment style.\n\nStable basic override:\n`assets/workflows/github-issue-triage-base/write-triage-report.md`\n\nStable advanced step:\n`github-issue-triage-base` step `write-triage-report`\n\nBasic example: local label and severity policy.\n\nCreate `assets/workflows/github-issue-triage-base/write-triage-report.md` in\nyour city assets:\n\n```markdown\nApply the repository triage policy before writing `triage-report.md`.\n\n- If the issue touches `internal/api/`, `docs/schema/openapi.*`, or generated\n dashboard client types, read `engdocs/architecture/api-control-plane.md` and\n `engdocs/contributors/huma-usage.md` before assigning priority.\n- Mark `needs_info` when the report lacks a reproduction, failing test, stack\n trace, linked CI artifact, or exact version. Do not invent a reproduction.\n- Use priority `p1` only when current `origin/main` is affected or data loss is\n plausible. Use `p2` for historical release-only regressions unless security\n or data integrity is involved.\n- Include suggested GitHub labels in the human-readable body, but keep the YAML\n front matter limited to the schema accepted by the validator.\n```\n\nAdvanced example: replace triage with a two-lane evidence pass and synthesis.\n\nCopy `github-issue-triage-base.formula.toml` into your local formula layer and\nreplace only `write-triage-report`:\n\n```toml\n[[steps]]\nid = \"write-triage-report\"\ntitle = \"Run product and engineering triage\"\nneeds = [\"reuse-current-body-hash\"]\nexpand = \"company-github-issue-triage-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe expansion should read the same `gc.github.snapshot_path` and write the same\n`triage-report.md` under `gc.github.triage_dir`. It may run separate product and\nengineering lanes, but its sink must still validate as\n`gc.github-issue-triage-report.v1` so `render-comment`, human gating, and\n`post-comment` continue to work.\n\n### Requirements Planning\n\nUse this when issue-fix requirements need local acceptance criteria, release\npolicy, customer-impact language, or compatibility constraints.\n\nStable basic override:\n`assets/workflows/github-issue-fix-base/generate-requirements.md`\n\nStable advanced step:\n`github-issue-fix-base` step `generate-requirements`\n\nBasic example: require W6H and example-mapping coverage.\n\nCreate `assets/workflows/github-issue-fix-base/generate-requirements.md`:\n\n```markdown\nGenerate requirements using the local planning standard.\n\n- Include Who, What, When, Where, Why, and How sections.\n- Add an Example Mapping section with at least one happy path, one negative\n path, and one edge case tied to the GitHub issue evidence.\n- Call out compatibility constraints for existing CLI flags, persisted bead\n metadata, and public GitHub comments.\n- Do not approve implementation until every acceptance criterion can be tested\n by a unit, integration, or explicit manual verification step.\n```\n\nAdvanced example: route requirements through a policy gate.\n\nCopy `github-issue-fix-base.formula.toml` and replace\n`generate-requirements`:\n\n```toml\n[[steps]]\nid = \"generate-requirements\"\ntitle = \"Generate policy-gated requirements\"\nneeds = [\"update-status-started\"]\nexpand = \"company-requirements-with-quality-gate\"\nmetadata = { \"gc.run_target\" = \"gc.requirements-planner\" }\n```\n\nThe replacement should still write the requirements artifact path back to the\nworkflow root metadata key expected by later implementation-plan and\ncreate-beads steps. It may add policy review, customer-impact review, or\napproval lanes before closing.\n\n### Implementation Plan\n\nUse this when implementation plans need local architecture constraints,\nmigration rules, rollout notes, or repository-specific boundaries.\n\nStable basic override:\n`assets/workflows/github-issue-fix-base/implementation-plan.md`\n\nStable advanced step:\n`github-issue-fix-base` step `implementation-plan`\n\nBasic example: enforce architecture docs for API changes.\n\nCreate `assets/workflows/github-issue-fix-base/implementation-plan.md`:\n\n```markdown\nBefore writing or updating `implementation-plan.md`, classify the affected area.\n\n- For `internal/api/`, CLI API-client code, SSE events, generated OpenAPI, or\n dashboard generated types, read `engdocs/architecture/api-control-plane.md`\n and `engdocs/contributors/huma-usage.md`.\n- Include a \"Wire Contract\" section explaining request/response/event type\n changes and generated-code impact.\n- Include a \"Migration and Rollback\" section for persisted metadata,\n database-like state, or external GitHub comments.\n- If the plan adds framework logic that could belong in prompt/config, call\n that out and choose the prompt/config path unless there is a clear SDK\n primitive requirement.\n```\n\nAdvanced example: replace the single-author implementation plan with\narchitecture and test-risk lanes.\n\nCopy `github-issue-fix-base.formula.toml` and replace `implementation-plan`:\n\n```toml\n[[steps]]\nid = \"implementation-plan\"\ntitle = \"Write implementation plan through architecture quorum\"\nneeds = [\"generate-requirements\"]\nexpand = \"company-architecture-design-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.design-author\" }\n```\n\nThe expansion can fan out to architecture, operations, and test-risk authors,\nthen synthesize one `implementation-plan.md`. The sink must still publish the\nimplementation-plan path in the same workflow-root metadata used by\n`design-review` and `create-beads`.\n\n### Design Review\n\nUse this when implementation plans need stricter approval rules or a broader\nreview group before bead creation begins.\n\nStable basic override:\n`assets/workflows/design-review/design-review.md`\n\nStable advanced steps:\n`design-review` step `design-review`; `github-issue-fix-base` step\n`design-review`\n\nBasic example: require security and operability review notes.\n\nCreate `assets/workflows/design-review/design-review.md`:\n\n```markdown\nReview the implementation plan with local release-readiness expectations.\n\n- Security-sensitive input, auth, GitHub token use, filesystem writes, and shell\n execution require explicit threat notes.\n- Operational changes need rollback, observability, and failure-mode notes.\n- Implementation plans that affect persisted bead metadata must identify the metadata keys,\n migration behavior, and how old runs remain readable.\n- Approve only when required changes are applied to the implementation plan, not\n deferred to implementation.\n```\n\nAdvanced example: replace the design review step with an N-wide review loop.\n\nCopy `design-review.formula.toml` and replace `design-review`:\n\n```toml\n[[steps]]\nid = \"design-review\"\ntitle = \"Run architecture, security, and test plan quorum\"\nexpand = \"company-design-review-n-wide\"\nmetadata = { \"gc.scope_ref\" = \"body\", \"gc.scope_role\" = \"member\", \"gc.on_fail\" = \"abort_scope\", \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe replacement may create separate review artifacts, but `finalize` should\nstill be able to determine whether the implementation plan is approved or\nblocked and write the terminal notification expected by the base workflow.\n\n### Create Beads\n\nUse this when task breakdown needs local slicing rules, dependency conventions,\nor bead/convoy naming standards.\n\nStable basic override:\n`assets/workflows/github-issue-fix-base/create-beads.md`\n\nStable advanced step:\n`github-issue-fix-base` step `create-beads`\n\nBasic example: enforce vertical slices and dependency hygiene.\n\nCreate `assets/workflows/github-issue-fix-base/create-beads.md`:\n\n```markdown\nCreate runnable implementation beads from the approved implementation plan.\n\n- Prefer vertical slices that each produce a testable behavior change.\n- Do not create \"refactor first\" tasks unless the implementation plan\n explicitly requires the refactor as a prerequisite for user-visible behavior.\n- Every task must identify expected files or modules, acceptance checks, and\n dependencies on earlier tasks.\n- Use nested `convoys[]` and `beads[]` in `tasks.md`; never use `epics[]`.\n```\n\nAdvanced example: replace bead creation with quality-gated task generation.\n\nCopy `github-issue-fix-base.formula.toml` and replace `create-beads`:\n\n```toml\n[[steps]]\nid = \"create-beads\"\ntitle = \"Create quality-gated implementation convoy\"\nneeds = [\"design-review\"]\nexpand = \"company-create-beads-with-architect-review\"\nmetadata = { \"gc.run_target\" = \"gc.task-decomposer\" }\n```\n\nThe expansion can draft tasks, run a bead-creation quality gate, revise until\napproved, create the task beads and convoy, and record the created mapping. It\nmust still produce the `tasks.md` shape consumed by\n`assets/scripts/create_beads_from_tasks.py` and record the created convoy\nmetadata expected by downstream build dispatch.\n\n### Post-Implementation Review\n\nUse this when the build needs different post-implementation review evidence,\nreview lanes, synthesis, or fix policy. Gap-analysis belongs in this review\nfanout instead of running as a separate lifecycle stage.\n\nStable basic override:\n`assets/workflows/build-basic/review.md`\n\nStable advanced steps:\n`build-base` step `review`\n\nBasic example: tune review evidence requirements.\n\nCreate `assets/workflows/build-basic/review.md`:\n\n```markdown\nRun implementation review with local release criteria.\n\n- Include the implementation summary, requirements coverage, changed files, and\n test commands in the review context.\n- Treat missing migration rollback notes as blocking for persisted metadata or\n schema changes.\n- Require `make test-fast-parallel` evidence when Go code changed, unless the\n implementation summary explains why a narrower test is sufficient.\n- If review fails, the fix pass must address only blocking findings, not\n optional cleanup.\n```\n\nAdvanced example: replace local review with an N-wide review and synthesize\nloop.\n\nOverride the `review` stage in a concrete child of `build-base`:\n\n```toml\n[[steps]]\nid = \"review\"\ntitle = \"Run company review quorum\"\nneeds = [\"implement\"]\nexpand = \"company-review-n-wide\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe expansion can run several independent reviewers, synthesize required\nfindings, run the required fix pass, and loop. Include a requirements coverage\nlane in the quorum so gap-analysis findings are handled with the rest of review.\nIts final output must be compatible with the base build expectation: pass means\nfinalize may run; fail means the workflow records actionable blocking findings.\n\n### Direct Implementation\n\nUse this when launching implementation directly against an approved convoy\nwithout the full build loop.\n\nStable basic override:\n`assets/workflows/implement/prepare.md`\n\nStable advanced steps:\n`implement` steps `prepare`, `drain-separate`, `drain-same-session`,\n`wait-for-drain`, `summarize`\n\nBasic example: add local preflight checks before draining work.\n\nCreate `assets/workflows/implement/prepare.md`:\n\n```markdown\nValidate the implementation launch before any worker edits source files.\n\n- Confirm the input convoy contains only runnable implementation beads or nested\n convoys expected by the approved plan.\n- Read `context_path` when provided and reject paths outside the rig root.\n- Verify the working tree is not already on a protected release branch.\n- Do not edit source files, create commits, or run implementation loops in this\n launcher step.\n```\n\nAdvanced example: force a same-session implementation policy.\n\nCopy `implement.formula.toml` and replace the drain steps so only the shared\nlane remains:\n\n```toml\n[[steps]]\nid = \"drain-same-session\"\ntitle = \"Drain implementation in one shared session\"\nneeds = [\"prepare\"]\nmetadata = { \"gc.run_target\" = \"gc.implementation-worker\" }\ndescription_file = \"../assets/workflows/implement/drain-same-session.md\"\n\n[steps.drain]\ncontext = \"shared\"\nformula = \"do-work-item\"\non_item_failure = \"skip_remaining\"\nmember_access = \"exclusive\"\n\n[steps.drain.item]\nsingle_lane = true\n```\n\nKeep `wait-for-drain` and `summarize` compatible with the base drain manifest.\nIf your override changes the drain policy, make sure operators can still see\nwhich source anchors passed, failed, or were skipped.\n\n### Per-Item Implementation\n\nUse this for the worker behavior applied to each drained implementation item.\n\nStable basic override:\n`assets/workflows/do-work/implement.md`\n\nStable advanced steps:\n`do-work` steps `prepare-worktree`, `implement`, `close-source-anchor`;\n`do-work-item` step `implement-item`\n\nBasic example: require local test selection and worktree discipline.\n\nCreate `assets/workflows/do-work/implement.md`:\n\n```markdown\nImplement only the assigned source anchor.\n\n- Read `work_dir` from source-anchor metadata and `cd` there before editing.\n- Select tests based on the changed area and explain why they are sufficient.\n- For Go changes, prefer package-level tests first, then `make test-fast-parallel`\n before closing if the change spans packages.\n- Leave unrelated files and unassigned beads untouched. Do not close the source\n anchor; the close step owns that.\n```\n\nAdvanced example: replace each item with a build-test-repair loop.\n\nCopy `do-work.formula.toml` and replace `implement`:\n\n```toml\n[[steps]]\nid = \"implement\"\ntitle = \"Implement item through build-test-repair loop\"\nneeds = [\"prepare-worktree\"]\nexpand = \"company-implementation-item-loop\"\nmetadata = { \"gc.run_target\" = \"gc.implementation-worker\" }\n```\n\nThe loop can implement, test, repair, and self-review the item, but\n`close-source-anchor` must still be able to verify the source anchor outcome and\nclose it with `gc.outcome=pass`.\n\n### Gap Analysis\n\nUse this when the implementation-vs-plan comparison needs local acceptance\ncriteria, compliance checks, or traceability rules.\n\nStable basic override:\n`assets/workflows/gap-analysis/write-report.md`\n\nStable advanced step:\n`gap-analysis` step `write-report`\n\nBasic example: enforce acceptance-criteria traceability.\n\nCreate `assets/workflows/gap-analysis/write-report.md`:\n\n```markdown\nWrite the gap-analysis report with explicit traceability.\n\n- For every approved acceptance criterion, mark `met`, `partially_met`, or\n `missing`.\n- Link each `met` item to changed files, tests, commits, or artifacts.\n- Treat untested acceptance criteria as gaps unless the implementation summary\n explains a legitimate manual verification.\n- Use `verdict: fail` when any required behavior, migration note, or test\n evidence is missing.\n```\n\nAdvanced example: replace report writing with independent product and test gap\nlanes.\n\nCopy `gap-analysis.formula.toml` and replace `write-report`:\n\n```toml\n[[steps]]\nid = \"write-report\"\ntitle = \"Run product and test gap analysis\"\nneeds = [\"validate-context\"]\nexpand = \"company-gap-analysis-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.gap-analyst\" }\n```\n\nThe expansion may produce multiple internal reports, but its final sink must\nwrite `{{report_path}}` with `schema: gc.verdict-report.v1` and\n`verdict: pass|fail`.\n\n### Implementation Review\n\nUse this when code review needs repository-specific risk checks, reviewer\npersonas, or approval gates.\n\nStable basic override:\n`assets/workflows/review/write-report.md`\n\nStable advanced step:\n`review` step `write-report`\n\nBasic example: add local review blockers.\n\nCreate `assets/workflows/review/write-report.md`:\n\n```markdown\nWrite the implementation review report using local blocking criteria.\n\n- Findings must include file path, line or symbol, severity, and evidence.\n- Block on data loss risk, auth/token misuse, untyped API wires, missing\n rollback for persisted metadata, or tests that do not exercise the bug path.\n- Do not block on style-only comments unless they hide a maintainability or\n correctness risk.\n- Use `verdict: pass` only when no blocking findings remain.\n```\n\nAdvanced example: replace single review with three independent reviewers and a\nsynthesis pass.\n\nCopy `review.formula.toml` and replace `write-report`:\n\n```toml\n[[steps]]\nid = \"write-report\"\ntitle = \"Run three-lane implementation review\"\nneeds = [\"validate-context\"]\nexpand = \"company-implementation-review-quorum\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe replacement may use separate correctness, test-risk, and operations lanes.\nIt must synthesize one `{{report_path}}` verdict report with only actionable\nblocking findings.\n\n### GitHub PR Review\n\nUse this when PR comments need local review policy, human gating rules, or\ndifferent review depth before posting.\n\nStable basic override:\n`assets/workflows/github-pr-review/run-review.md`\n\nStable advanced steps:\n`github-pr-review` steps `run-review`, `human-gate-comment`, `post-comment`\n\nBasic example: require maintainer-grade PR findings.\n\nCreate `assets/workflows/github-pr-review/run-review.md`:\n\n```markdown\nReview the pull request as a maintainer.\n\n- Prioritize regressions, missing tests, security issues, data-loss risk, and\n public API breakage.\n- Each finding must include the PR diff location, why it is a real bug, and the\n smallest acceptable fix.\n- Do not post optional style suggestions as blocking findings.\n- If no blocking issues exist, say that clearly and mention the remaining test\n risk.\n```\n\nAdvanced example: replace PR review with multi-model review before rendering a\ncomment.\n\nCopy `github-pr-review.formula.toml` and replace `run-review`:\n\n```toml\n[[steps]]\nid = \"run-review\"\ntitle = \"Run multi-model PR review\"\nneeds = [\"reuse-current-head\"]\nexpand = \"company-pr-review-multi-model\"\nmetadata = { \"gc.run_target\" = \"gc.review-synthesizer\" }\n```\n\nThe expansion should still produce the review artifact consumed by\n`render-comment`. Keep `post_mode=human_gate` behavior intact when configured.\n\n### Publish\n\nUse this when publishing needs local branch, PR title, protected-branch, or\nrelease policy.\n\nStable basic override:\n`assets/workflows/publish/preflight.md`\n\nStable advanced steps:\n`publish` steps `preflight`, `push`, `open-pr`\n\nBasic example: enforce branch and PR policy.\n\nCreate `assets/workflows/publish/preflight.md`:\n\n```markdown\nValidate publish policy before pushing or opening a PR.\n\n- Never push directly to `main`, release branches, or protected environment\n branches.\n- PR titles must include the issue or convoy identifier when available.\n- PR bodies must include tests run, rollout risk, and rollback notes when\n persisted state or external comments changed.\n- Fail closed if credentials cannot prove the remote update is lease-checked or\n create-if-absent.\n```\n\nAdvanced example: replace PR creation with a release-train handoff.\n\nCopy `publish.formula.toml` and replace `open-pr`:\n\n```toml\n[[steps]]\nid = \"open-pr\"\ntitle = \"Create release-train handoff\"\nneeds = [\"push\"]\nexpand = \"company-release-train-handoff\"\nmetadata = { \"gc.run_target\" = \"gc.publisher\" }\n```\n\nThe replacement can open a PR, create a release-train ticket, or request human\napproval, but it must leave a durable final report path and terminal publish\nstatus for the caller.\n\nBy default artifacts go under the target rig:\n\n```text\n/plans//\n requirements.md\n implementation-plan.md\n tasks.md\n context.yaml\n build/final-report.md\n```\n\nThe mayor may use a different artifact root when the user explicitly asks for\none. If the default `plans/` directory already exists and appears unrelated to\nGC planning artifacts, the helper falls back to `/gc-plans`. The same\n`/` structure should be used under the override root.\n\nThe mayor uses `assets/scripts/create_beads_from_tasks.py` after approving a\ntask plan. The script requires Python 3 with PyYAML available, invokes `gc bd --rig\n` for runnable beads, invokes `gc convoy --rig ` for\nconvoy heads and membership, and records the created mapping in `tasks.md`.\n\nThe `tasks.md` payload uses nested `convoys[]` and `beads[]`; `epics[]` is a\nhard validation error. `convoys[].dependencies` expands to runnable bead edges\nfrom the upstream terminal runnable beads to the downstream root runnable beads.\n\nContext bundles are YAML or JSON files with:\n\n```yaml\nitems:\n - name: Requirements\n path: requirements.md\n description: Product requirements and acceptance criteria.\n```\n\nEach item has only `name`, `path`, and `description`. Validate with:\n\n```sh\npython3 /assets/scripts/validate_context_bundle.py context.yaml --allow-root \n```\n\nGap-analysis and review reports use `schema: gc.verdict-report.v1` front matter\nwith `verdict: pass|fail`. Validate with:\n\n```sh\npython3 /assets/scripts/validate_verdict_report.py report.md --kind review\n```\n\n## GitHub Adapter Workflows\n\nThe GitHub workflows are targetless `graph.v2` formulas. They accept only full\ncanonical URLs:\n\n```text\nhttps://github.com///issues/\nhttps://github.com///pull/\n```\n\nLaunch triage:\n\n```sh\ngc sling gc.run-operator github-issue-triage --formula \\\n --var github_issue_url=https://github.com///issues/\n```\n\nCustomize triage behavior by setting `triage_rubric_path` to a Markdown\nrubric/prompt, either at launch or in a rig's `formula_vars`. The rubric can\ncarry project-specific label policy, priority semantics, investigation rules,\nor a richer report skeleton. The base formula still owns the metadata handoff,\nreport schema, validator, security gate, and comment protocol.\n\nFor deeper customization, create a local `github-issue-triage` formula that\nextends `github-issue-triage-base` and overrides only `write-triage-report`.\nThat replacement step can inline an expansion or delegate to another workflow,\nbut it should still read the pack-owned GitHub metadata from the workflow root\nbead and write the same `gc.github-issue-triage-report.v1` sink metadata.\n\nLaunch PR review:\n\n```sh\ngc sling gc.run-operator github-pr-review --formula \\\n --var github_pr_url=https://github.com///pull/ \\\n --var post_mode=human_gate\n```\n\nLaunch issue fix:\n\n```sh\ngc sling gc.run-operator github-issue-fix --formula \\\n --var github_issue_url=https://github.com///issues/ \\\n --var mode=interactive \\\n --var pr_mode=none \\\n --var drain_policy=separate\n```\n\nGitHub API calls go through wrapper scripts in\n`/assets/scripts/`. Formulas should call those wrappers, not `gh`\ndirectly, except when diagnosing wrapper failures." }, "og_image": "/og/gascity.svg", "latest": "0.1.6", @@ -236,10 +236,10 @@ "description": "Default Gas Town coding workflow pack.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/gastown", "source_kind": "git", - "search_text": "gastown gascity-packs maintained gas city default gas town coding workflow pack. https://github.com/gastownhall/gascity-packs/tree/main/gastown git # gastown pack gastown is the domain-specific coding workflow pack. it provides the city coordinator roles, rig worker roles, patrol formulas, and the pack-local dog pool used for stuck-agent shutdown warrants. ## import ```toml [imports.gastown] source = \"../packs/gastown\" ``` use the pack as the workspace pack for city-scoped agents and as a rig pack for rig-scoped agents. ## composition gas city composes the builtin core pack (mechanical housekeeping orders) through the explicit `includes` entries that `gc init` writes into city.toml; this pack composes alongside it via `[imports.gastown]`. the retired maintenance pack no longer exists: gastown's `mol-shutdown-dance` and dog prompt fragments (`propulsion-dog`, `architecture`, `following-mol`) are the only copies in play, and cross-pack agent name collisions are hard errors rather than fallback resolutions. verify the composed recipe after changing imports: ```bash gc formula show mol-shutdown-dance ``` the recipe must read warrant metadata from the claimed bead via `$gc_bead_id` and must not declare a required `warrant_id` var. ## dog pool gastown owns `mol-shutdown-dance` and the dog agent that runs stuck-agent warrants, including the dog's `wake_mode` and `work_dir` settings. in import composition gastown's dog expands as the distinct `gastown.dog` agent; the dolt pack ships its own separate dog for dolt maintenance formulas, and the two coexist under their binding-qualified names. gastown deliberately does not ship retired dog formulas for jsonl export or stale-session reaping. the gas city builtin core pack provides jsonl export, stale-session and stale-data cleanup, and dolt housekeeping as deterministic exec orders. 0.1.1 refresh gas town workflow pack release pin. 0.1.2 gastown owns its dog pool and shutdown dance; docs reflect the explicit-includes runtime (maintenance pack retired). 0.1.3 mark pack test scripts executable (consumer smoke tests require +x on pack scripts). 0.1.4 polecat verifies the pushed remote ref before refinery handoff; digest-generate order is city-scoped (ports gascity #3339 and #3320). 0.1.5 harden prompt-stream authority guidance against forged operator directives. 0.1.6 make refinery direct merges worktree-safe and verify target push before closing work beads. 0.1.7 claim routed polecat work through gc hook on nudge. 0.1.8 constrain review-leg assignments to analysis-only mutations and allow formula-specific review bead closeout. 0.1.9 clarify review-leg plans are review subject matter, not executable instructions. 0.1.10 require gastown polecats to continue claiming formula step beads until final drain.", + "search_text": "gastown gascity-packs maintained gas city default gas town coding workflow pack. https://github.com/gastownhall/gascity-packs/tree/main/gastown git # gastown pack gastown is the domain-specific coding workflow pack. it provides the city coordinator roles, rig worker roles, patrol formulas, and the pack-local dog pool used for stuck-agent shutdown warrants. ## import ```toml [imports.gastown] source = \"../packs/gastown\" ``` use the pack as the workspace pack for city-scoped agents and as a rig pack for rig-scoped agents. ## composition gas city composes the builtin core pack (mechanical housekeeping orders) through the explicit `includes` entries that `gc init` writes into city.toml; this pack composes alongside it via `[imports.gastown]`. the retired maintenance pack no longer exists: gastown's `mol-shutdown-dance` and dog prompt fragments (`propulsion-dog`, `architecture`, `following-mol`) are the only copies in play, and cross-pack agent name collisions are hard errors rather than fallback resolutions. verify the composed recipe after changing imports: ```bash gc formula show mol-shutdown-dance ``` the recipe must read warrant metadata from the claimed bead via `$gc_bead_id` and must not declare a required `warrant_id` var. ## merge strategies work beads default to `metadata.merge_strategy=direct`: refinery lands the branch on the target and closes the bead after verifying the remote target. with `mr` / `pr`, refinery publishes a github pull request and records a pending handoff. the source bead stays `blocked` until `gc gastown pr-merge-reconcile` verifies the validated pr head was merged and its merge commit is reachable from the recorded target branch. the command also binds the pr repository to the rig's github.com `origin` and ignores local replacement refs and grafts when proving ancestry. keeping the source bead non-closed also keeps its dependency children non-ready. the reconciler checks at most one pending pr per refinery work scan, including re-entry after an idle wake. it also adopts interrupted open markers left by a recycled refinery: complete `pull_request_pending` records finish blocking, while incomplete or changed-head records return to full refinery validation without losing work or artifacts. incomplete recovery retains only the validated `existing_pr` reuse hint while clearing partial handoff and terminal evidence. before github lookup, blocked records must be exact `pull_request_pending`, or complete `mr_merged` evidence retained after a verified close failure; other blocked lifecycle states are quarantined. open prs remain pending, changed open heads return to refinery quality gates, and closed-unmerged, contradictory, or merged-unvalidated prs remain blocked for operator review. a verified close records the distinct `merge_result=mr_merged` state and retains both the validated `pr_head_sha` and exact `polecat/` source branch through artifact cleanup. cleanup is never invoked by the mr publication path. because blocked beads are not session demand, the witness patrol wakes the refinery whenever a pending pr check is due, so a merged pr releases its dependents even when no other merge work arrives. this contract applies to new handoffs. an upgrade does not reopen legacy beads that an older pack already closed at pr publication; operators should audit those closed `merge_result=pull_request` records against their pr state before relying on their dependency edges. ## default sling formula the polecat agent ships an agent-level default sling formula, so a plain `gc sling /gastown.polecat \"\"` compiles `mol-polecat-work` (`method = \"default-on-formula\"`) instead of routing a bare bead. that agent-level value outranks a city's `[agent_defaults] default_sling_formula`: `agent.effectivedefaultslingformula()` returns the agent's own value before any inherited one, and `applyagentdefaults` fills the city default in only for agents that set neither their own nor an inherited pack default. a city that deliberately routed polecat slings through its own default formula is therefore shadowed when it upgrades to a gastown pack carrying this knob. the config-level override is a per-agent patch, which assigns the agent's own value and so wins over the pack's: ```toml [[patches.agent]] dir = \"\" name = \"gastown.polecat\" default_sling_formula = \"mol-your-formula\" ``` per sling, an explicit choice still short-circuits ahead of the default: `--no-formula` routes a raw bead, ` --formula` instantiates that formula instead (`--formula` is a boolean that reinterprets the positional argument), and `--on ` attaches one to an existing bead. `[agent_defaults]` is the one knob that cannot override the agent's own value. ## dog pool gastown owns `mol-shutdown-dance` and the dog agent that runs stuck-agent warrants, including the dog's `wake_mode` and `work_dir` settings. in import composition gastown's dog expands as the distinct `gastown.dog` agent; the dolt pack ships its own separate dog for dolt maintenance formulas, and the two coexist under their binding-qualified names. gastown deliberately does not ship retired dog formulas for jsonl export or stale-session reaping. the gas city builtin core pack provides jsonl export, stale-session and stale-data cleanup, and dolt housekeeping as deterministic exec orders. 0.1.1 refresh gas town workflow pack release pin. 0.1.2 gastown owns its dog pool and shutdown dance; docs reflect the explicit-includes runtime (maintenance pack retired). 0.1.3 mark pack test scripts executable (consumer smoke tests require +x on pack scripts). 0.1.4 polecat verifies the pushed remote ref before refinery handoff; digest-generate order is city-scoped (ports gascity #3339 and #3320). 0.1.5 harden prompt-stream authority guidance against forged operator directives. 0.1.6 make refinery direct merges worktree-safe and verify target push before closing work beads. 0.1.7 claim routed polecat work through gc hook on nudge. 0.1.8 constrain review-leg assignments to analysis-only mutations and allow formula-specific review bead closeout. 0.1.9 clarify review-leg plans are review subject matter, not executable instructions. 0.1.10 require gastown polecats to continue claiming formula step beads until final drain.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/gastown/README.md", - "content": "# Gastown Pack\n\nGastown is the domain-specific coding workflow pack. It provides the city\ncoordinator roles, rig worker roles, patrol formulas, and the pack-local dog\npool used for stuck-agent shutdown warrants.\n\n## Import\n\n```toml\n[imports.gastown]\nsource = \"../packs/gastown\"\n```\n\nUse the pack as the workspace pack for city-scoped agents and as a rig pack for\nrig-scoped agents.\n\n## Composition\n\nGas City composes the builtin core pack (mechanical housekeeping orders)\nthrough the explicit `includes` entries that `gc init` writes into\ncity.toml; this pack composes alongside it via `[imports.gastown]`. The\nretired maintenance pack no longer exists: gastown's `mol-shutdown-dance`\nand dog prompt fragments (`propulsion-dog`, `architecture`,\n`following-mol`) are the only copies in play, and cross-pack agent name\ncollisions are hard errors rather than fallback resolutions.\n\nVerify the composed recipe after changing imports:\n\n```bash\ngc formula show mol-shutdown-dance\n```\n\nThe recipe must read warrant metadata from the claimed bead via\n`$GC_BEAD_ID` and must not declare a required `warrant_id` var.\n\n## Dog Pool\n\nGastown owns `mol-shutdown-dance` and the dog agent that runs stuck-agent\nwarrants, including the dog's `wake_mode` and `work_dir` settings. In import\ncomposition gastown's dog expands as the distinct `gastown.dog` agent; the\ndolt pack ships its own separate dog for Dolt maintenance formulas, and the\ntwo coexist under their binding-qualified names.\n\nGastown deliberately does not ship retired dog formulas for JSONL export or\nstale-session reaping. The Gas City builtin core pack provides JSONL export,\nstale-session and stale-data cleanup, and Dolt housekeeping as deterministic\nexec orders." + "content": "# Gastown Pack\n\nGastown is the domain-specific coding workflow pack. It provides the city\ncoordinator roles, rig worker roles, patrol formulas, and the pack-local dog\npool used for stuck-agent shutdown warrants.\n\n## Import\n\n```toml\n[imports.gastown]\nsource = \"../packs/gastown\"\n```\n\nUse the pack as the workspace pack for city-scoped agents and as a rig pack for\nrig-scoped agents.\n\n## Composition\n\nGas City composes the builtin core pack (mechanical housekeeping orders)\nthrough the explicit `includes` entries that `gc init` writes into\ncity.toml; this pack composes alongside it via `[imports.gastown]`. The\nretired maintenance pack no longer exists: gastown's `mol-shutdown-dance`\nand dog prompt fragments (`propulsion-dog`, `architecture`,\n`following-mol`) are the only copies in play, and cross-pack agent name\ncollisions are hard errors rather than fallback resolutions.\n\nVerify the composed recipe after changing imports:\n\n```bash\ngc formula show mol-shutdown-dance\n```\n\nThe recipe must read warrant metadata from the claimed bead via\n`$GC_BEAD_ID` and must not declare a required `warrant_id` var.\n\n## Merge strategies\n\nWork beads default to `metadata.merge_strategy=direct`: refinery lands the\nbranch on the target and closes the bead after verifying the remote target.\nWith `mr` / `pr`, refinery publishes a GitHub pull request and records a\npending handoff. The source bead stays `blocked` until\n`gc gastown pr-merge-reconcile` verifies the validated PR head was merged and\nits merge commit is reachable from the recorded target branch. The command\nalso binds the PR repository to the rig's github.com `origin` and ignores\nlocal replacement refs and grafts when proving ancestry. Keeping the source\nbead non-closed also keeps its dependency children non-ready.\n\nThe reconciler checks at most one pending PR per refinery work scan, including\nre-entry after an idle wake. It also adopts interrupted open markers left by a\nrecycled refinery: complete `pull_request_pending` records finish blocking,\nwhile incomplete or changed-head records return to full refinery validation\nwithout losing work or artifacts. Incomplete recovery retains only the\nvalidated `existing_pr` reuse hint while clearing partial handoff and terminal\nevidence. Before GitHub lookup, blocked records must be exact\n`pull_request_pending`, or complete `mr_merged` evidence retained after a\nverified close failure; other blocked lifecycle states are quarantined. Open\nPRs remain pending, changed open heads return to refinery quality gates, and\nclosed-unmerged, contradictory, or merged-unvalidated PRs remain blocked for\noperator review. A verified close records the distinct\n`merge_result=mr_merged` state and retains both the validated `pr_head_sha` and\nexact `polecat/` source branch through artifact cleanup. Cleanup is never\ninvoked by the MR publication path. Because blocked beads are not session\ndemand, the witness patrol wakes the refinery whenever a pending PR check is\ndue, so a merged PR releases its dependents even when no other merge work\narrives.\n\nThis contract applies to new handoffs. An upgrade does not reopen legacy beads\nthat an older pack already closed at PR publication; operators should audit\nthose closed `merge_result=pull_request` records against their PR state before\nrelying on their dependency edges.\n\n## Default Sling Formula\n\nThe polecat agent ships an agent-level default sling formula, so a plain\n`gc sling /gastown.polecat \"\"` compiles `mol-polecat-work`\n(`method = \"default-on-formula\"`) instead of routing a bare bead.\n\nThat agent-level value outranks a city's `[agent_defaults]\ndefault_sling_formula`: `Agent.EffectiveDefaultSlingFormula()` returns the\nagent's own value before any inherited one, and `ApplyAgentDefaults` fills the\ncity default in only for agents that set neither their own nor an inherited pack\ndefault. A city that deliberately routed polecat slings through its own default\nformula is therefore shadowed when it upgrades to a gastown pack carrying this\nknob.\n\nThe config-level override is a per-agent patch, which assigns the agent's own\nvalue and so wins over the pack's:\n\n```toml\n[[patches.agent]]\ndir = \"\"\nname = \"gastown.polecat\"\ndefault_sling_formula = \"mol-your-formula\"\n```\n\nPer sling, an explicit choice still short-circuits ahead of the default:\n`--no-formula` routes a raw bead, ` --formula` instantiates that formula\ninstead (`--formula` is a boolean that reinterprets the positional argument), and\n`--on ` attaches one to an existing bead. `[agent_defaults]` is the one\nknob that cannot override the agent's own value.\n\n## Dog Pool\n\nGastown owns `mol-shutdown-dance` and the dog agent that runs stuck-agent\nwarrants, including the dog's `wake_mode` and `work_dir` settings. In import\ncomposition gastown's dog expands as the distinct `gastown.dog` agent; the\ndolt pack ships its own separate dog for Dolt maintenance formulas, and the\ntwo coexist under their binding-qualified names.\n\nGastown deliberately does not ship retired dog formulas for JSONL export or\nstale-session reaping. The Gas City builtin core pack provides JSONL export,\nstale-session and stale-data cleanup, and Dolt housekeeping as deterministic\nexec orders." }, "og_image": "/og/gastown.svg", "latest": "0.1.10", @@ -362,13 +362,13 @@ "description": "garrytan/gstack founder sprint (office hours, autoplan, QA, security, release readiness) as a Gas City build factory.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/gstack", "source_kind": "git", - "search_text": "gstack gascity-packs maintained gas city garrytan/gstack founder sprint (office hours, autoplan, qa, security, release readiness) as a gas city build factory. https://github.com/gastownhall/gascity-packs/tree/main/gstack git # gstack gas city pack this pack adapts the [garrytan/gstack](https://github.com/garrytan/gstack) methodology — a founder-style sprint that runs yc-office-hours intake, multi-perspective plan review, staff code review, qa, security review, and release readiness — into durable gas city workflows. you get one full-lifecycle formula, `gstack-build`, that keeps the recognizable garrytan/gstack sprint: think -> plan -> build -> review -> test -> ship -> reflect upstream gstack is a claude code skills pack whose roles (yc office hours, ceo/founder review, engineering review, design review, staff review, qa, cso security, documentation, release engineering) hand work to each other through subagents. in this pack those roles are providerless gas city agents, and their multi-agent handoffs are gas city fanouts: persistent, observable, and retryable through the workflow graph. ## when to choose gstack - you want the strictest pre-ship gating of the methodology packs: gstack adds dedicated `qa` and `release-readiness` stages between code review and finalize, so nothing finalizes without browser-oriented qa evidence and a documentation/ship/deploy readiness pass. - you want founder- and pm-flavored review perspectives. plan review fans out ceo/founder scope, design, engineering, and developer-experience lanes; requirements come from an office-hours interrogation (demand, status quo, user specificity, narrow wedge) rather than a plain requirements template. - you are comfortable answering questions during the run. unlike `build-basic` and the other methodology packs, gstack defaults both `interaction_mode` and `review_mode` to `interactive`, because raw gstack is intentionally conversation-heavy. you can override both for automation. - if you want a leaner run, pick `build-basic` (single-lane review, no extra gates) or another methodology pack (compound engineering, superpowers, bmad); they share the same `build-base` contract, so switching later is a one-line formula change. ## quick start these steps go from a fresh machine to a completed gstack sprint. 1. install gas city and create a city: ```sh brew install gascity gc init ~/my-city && cd ~/my-city && gc start ``` 2. add the repository you want to build in as a rig: ```sh mkdir proj && cd proj && git init && gc rig add . ``` 3. import this pack at city scope. from the city directory — this writes the import, fetches the latest release, and pins it in `packs.lock`, no clone needed: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//gstack ``` then import the gas city roles pack on the rig: the methodology pack supplies the formulas and `gstack.*` agents, and the rig-level `gascity/roles` import supplies the `gc.*` role agents (run operator, publisher, implementation workers) that the formulas route to. in your `city.toml`, then `gc import install`: ```toml [[rigs]] name = \"proj\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/gstack`) instead. 4. create a bead for the goal and sling it onto `gstack-build`. the formula declares `target_required = true`, so it is launched against a bead (or convoy), not with `--formula`: ```sh gc bd create \"add csv export to the reports page\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/csv-export/build \\ --var drain_policy=separate ``` 5. the run walks the build-base stage sequence. with gstack's overrides and additions, that means: `prepare`, office-hours `requirements`, autoplan `plan`, the `plan-review` fanout (founder scope, design, engineering, developer experience), `decompose` into an implementation convoy, `implement` (a drain of `gstack-work` items), the `review` fanout (staff, qa evidence, cso security, gap analysis), then the two pack-added gates — `qa` (browser qa plus regression-test evidence) and `release-readiness` (documentation, ship readiness, deployment readiness) — before `finalize` writes the sprint report and `publish` optionally pushes or opens a pr. in the default interactive modes, expect the office-hours and review lanes to ask you questions before proceeding. 6. artifacts land under your `artifact_root` (here `plans/csv-export/build/` in the rig): requirements, plan, decomposition, review report, qa summary, release-readiness summary, and the final sprint report. inspect the formula surface at any time with: ```sh gc formula catalog --json gc formula show gstack-build --json ``` ## stage map `gstack-build` extends `build-base` and keeps the inherited anchor order `prepare -> requirements -> plan -> plan-review -> decompose -> implement/implement-same-session -> review -> finalize -> publish`. no base anchor is renamed, skipped, or reordered; `prepare` stays inherited. | stage | gstack behavior | route | | --- | --- | --- | | `requirements` | office-hours intake: demand, status quo, user specificity, narrow wedge, observation, future fit | `gstack.office-hours` | | `plan` | autoplan draft | `gstack.founder-reviewer` | | `plan-review` | `gstack-plan-review` fanout: founder scope, design, engineering, developer-experience lanes | `gstack.review-synthesizer` | | `decompose` | implementation convoy creation | `gstack.decomposer` | | `implement` / `implement-same-session` | drains `gstack-work` (separate) or `gstack-work-item` (same-session) | `{implementation_target}` | | `review` | `gstack-code-review` fanout: staff, qa-evidence, cso-security, gap-analysis lanes | `gstack.review-synthesizer` | | `qa` (pack-added) | `gstack-qa-review` fanout: browser qa and regression-test evidence | `gstack.qa-lead` | | `release-readiness` (pack-added) | `gstack-release-readiness` fanout: documentation, ship readiness, deployment readiness | `gstack.release-engineer` | | `finalize` | sprint report under the artifact root | `gstack.release-engineer` | | `publish` | optional push / pr lane | `gc.publisher` | `qa` and `release-readiness` are the only pack-added steps, anchored after `review` and before `finalize`: `qa` needs `review`, `release-readiness` needs `qa`, `finalize` is rewired to need `release-readiness`, and `publish` still needs `finalize`. each expands a check-gated loop (`implementation-review-approved.sh`; qa allows 6 attempts, release-readiness 4) whose final lane (`synthesize-qa`, `synthesize-release-readiness`) owns the `code_review.verdict=done|iterate` loop verdict. their outputs are the approved qa summary recorded on the workflow root at `gc.build.qa_summary_path` before release readiness begins and the approved readiness summary at `gc.build.release_readiness_summary_path` before finalize begins. the `review` anchor works the same way: `gstack-code-review` records the review context, fans out the staff, qa-evidence, cso-security, and gap-analysis lanes, fans in at `synthesize-code-review`, and loops an `apply-review-findings` lane (routed to the caller-selected implementation target) through a bounded graph check until `code_review.verdict=done` lands on the workflow root. `gstack-fix-loop` carries the same review-fix contract for standalone adapter use. supported modes and drain policies, as declared in `[metadata.gc.methodology]` of `gstack-build`: - `interaction_modes`: `interactive`, `autonomous`, `headless` (inherited `interaction_mode` var; the pack pins the default to `interactive` because raw gstack is conversation-heavy) - `review_modes`: `report`, `agent`, `interactive` (inherited `review_mode` var; the pack pins the default to `interactive` to match the office-hours posture) - `implementation_strategy`: `drain` with `allowed_drain_policies` of `separate` (drains `gstack-work` item formulas with exclusive member access) and `same-session` (drains `gstack-work-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`) the native stage formulas extend the matching base methodology contracts: `gstack-planning` (`planning-base`), `gstack-decomposition` (`decomposition-base`), `gstack-implementation` (`implement`), `gstack-review` (`code-review-base`), and `gstack-fix-loop` (`fix-loop-base`), plus the drain formulas `gstack-work` (`do-work`) and `gstack-work-item` (`do-work-item`). ## customization pass any of these as `--var k=v` on the launch command. the selector vars also let shared gas city adapters pick gstack stages without using `gstack-build`. | variable | default | what it changes | | --- | --- | --- | | `interaction_mode` | `interactive` | human participation in planning and gates. `interactive` preserves blocking questions and approval menus; `autonomous` records assumptions instead of asking; `headless` never blocks. note: gstack defaults this to `interactive`, unlike the other methodology packs. | | `review_mode` | `interactive` | review authority. `report` writes findings only, without applying fixes or opening release paths; `agent` is a structured machine handoff whose fixes are applied by the loop; `interactive` preserves a raw top-level review that may apply safe fixes. also defaulted to `interactive` here. | | `drain_policy` | `separate` | `separate` runs each implementation bead in its own worktree lane in parallel by convoy dependencies; `same-session` runs all items in one shared single-lane session. | | `implementation_target` | `gstack.implementer` | the role that implements drained work items and applies review/qa fix findings. | | `artifact_root` | (required input) | directory under the rig where all stage artifacts land. | | `push` | `false` | allow the publish stage to push after all checks pass. | | `open_pr` | `false` | allow the publish stage to open a pr after all checks pass. | | `max_iterations` | `10` | maximum implementation/review fix attempts. | | `planning_formula` | `gstack-planning` | selector: planning methodology formula. | | `decomposition_formula` | `gstack-decomposition` | selector: decomposition methodology formula. | | `implementation_formula` | `gstack-implementation` | selector: implementation entry formula. | | `implementation_item_formula` | `gstack-work-item` | selector: single-item implementation formula for shared drains. | | `code_review_formula` | `gstack-review` | selector: code-review methodology formula. | | `review_fix_formula` | `gstack-fix-loop` | selector: review-fix loop formula. | step prompt text is also customizable without touching the formula graph. each stage reads its body from `assets/workflows/gstack-build/.md`, and a file at the same relative path in a higher-priority city or local pack layer shadows it. for example, to tighten the qa gate, create `assets/workflows/gstack-build/qa.md` in your city assets: ```markdown run gstack qa against the staging deployment, not a local build. - exercise the changed user flow end to end in a real browser session. - attach the regression test command output for every changed package. - treat missing reproduction steps for any found defect as iterate, not done. ``` for the full customization contract (basic asset shadowing and advanced step override rules), see \"stable workflow override interface\" in the [gascity pack readme](../gascity/readme.md). ## examples interactive sprint on a new feature (all defaults: interactive intake and review, separate drain): ```sh gc bd create \"add csv export to the reports page\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/csv-export/build \\ --var drain_policy=separate ``` autonomous run that pushes and opens a pr — overrides both interactive defaults so no lane blocks on a human, and enables the publish side effects: ```sh gc bd create \"migrate the settings page to the v2 form components\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/settings-v2/build \\ --var drain_policy=separate \\ --var interaction_mode=autonomous \\ --var review_mode=agent \\ --var push=true \\ --var open_pr=true ``` same-session drain for a small change — keeps every implementation item in one shared worktree and conversation instead of parallel lanes: ```sh gc bd create \"fix the off-by-one in pagination page counts\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/pagination-fix/build \\ --var drain_policy=same-session ``` ## what's vendored the vendored upstream files under `vendor/gstack` are reference material; `vendor/gstack/upstream.toml` records the upstream source repository, the pinned commit, and the mit license. the installed files under `skills/` (office-hours, autoplan, plan-eng-review, review, investigate, document-release) expose the same vocabulary for agents. runtime execution is owned by formulas, beads, and gas city graph lanes. raw-framework subagents become gas city fanouts. do not preserve upstream subagent behavior as provider-native subagents; model that work as formulas or expansion children. every lane in this pack routes through `gc.run_target` to a providerless `gstack.*` agent, a `gc.*` role, or the caller-selected implementation target — no lane dispatches provider-native subagents. ## compatibility ledger the pack-local compatibility ledger lives at [`gstack/requirements.md`](./requirements.md) and records the build-base contract proofs — the inherited `gc` import, the preserved anchor order, the qa/release-readiness insertion between review and finalize — together with the evidence commands that reproduce each claim. 0.1.0 first registry release of the gstack build factory.", + "search_text": "gstack gascity-packs maintained gas city garrytan/gstack founder sprint (office hours, autoplan, qa, security, release readiness) as a gas city build factory. https://github.com/gastownhall/gascity-packs/tree/main/gstack git # gstack gas city pack this pack adapts the [garrytan/gstack](https://github.com/garrytan/gstack) methodology — a founder-style sprint that runs yc-office-hours intake, multi-perspective plan review, staff code review, qa, security review, and release readiness — into durable gas city workflows. you get one full-lifecycle formula, `gstack-build`, that keeps the recognizable garrytan/gstack sprint: think -> plan -> build -> review -> test -> ship -> reflect upstream gstack is a claude code skills pack whose roles (yc office hours, ceo/founder review, engineering review, design review, staff review, qa, cso security, documentation, release engineering) hand work to each other through subagents. in this pack those roles are providerless gas city agents, and their multi-agent handoffs are gas city fanouts: persistent, observable, and retryable through the workflow graph. ## when to choose gstack - you want the strictest pre-ship gating of the methodology packs: gstack adds dedicated `qa` and `release-readiness` stages between code review and finalize, so nothing finalizes without browser-oriented qa evidence and a documentation/ship/deploy readiness pass. - you want founder- and pm-flavored review perspectives. plan review fans out ceo/founder scope, design, engineering, and developer-experience lanes; requirements come from an office-hours interrogation (demand, status quo, user specificity, narrow wedge) rather than a plain requirements template. - you are comfortable answering questions during the run. unlike `build-basic` and the other methodology packs, gstack defaults both `interaction_mode` and `review_mode` to `interactive`, because raw gstack is intentionally conversation-heavy. you can override both for automation. - if you want a leaner run, pick `build-basic` (single-lane review, no extra gates) or another methodology pack (compound engineering, superpowers, bmad); they share the same `build-base` contract, so switching later is a one-line formula change. ## quick start these steps go from a fresh machine to a completed gstack sprint. 1. install gas city and create a city: ```sh brew install gascity gc init ~/my-city && cd ~/my-city && gc start ``` 2. add the repository you want to build in as a rig: ```sh mkdir proj && cd proj && git init && gc rig add . ``` 3. import this pack at city scope. from the city directory — this writes the import, fetches the latest release, and pins it in `packs.lock`, no clone needed: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//gstack ``` then import the gas city roles pack on the rig: the methodology pack supplies the formulas and `gstack.*` agents, and the rig-level `gascity/roles` import supplies the `gc.*` role agents (run operator, publisher, implementation workers) that the formulas route to. in your `city.toml`, then `gc import install`: ```toml [[rigs]] name = \"proj\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/gstack`) instead. 4. create a bead for the goal and sling it onto `gstack-build`. the formula declares `target_required = true`, so it is launched against a bead (or convoy), not with `--formula`: ```sh gc bd create \"add csv export to the reports page\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/csv-export/build \\ --var drain_policy=separate ``` 5. the run walks the build-base stage sequence. with gstack's overrides and additions, that means: `prepare`, office-hours `requirements`, autoplan `plan`, the `plan-review` fanout (founder scope, design, engineering, developer experience), `decompose` into an implementation convoy, `implement` (a drain of `gstack-work` items), the `review` fanout (staff, qa evidence, cso security, gap analysis), then the two pack-added gates — `qa` (browser qa plus regression-test evidence) and `release-readiness` (documentation, ship readiness, deployment readiness) — before `finalize` writes the sprint report and `publish` optionally pushes or opens a pr. in the default interactive modes, expect the office-hours and review lanes to ask you questions before proceeding. 6. artifacts land under your `artifact_root` (here `plans/csv-export/build/` in the rig): requirements, plan, decomposition, review report, qa summary, release-readiness summary, and the final sprint report. inspect the formula surface at any time with: ```sh gc formula catalog --json gc formula show gstack-build --json ``` ## stage map `gstack-build` extends `build-base` and keeps the inherited anchor order `prepare -> requirements -> plan -> plan-review -> decompose -> implement/implement-same-session -> review -> finalize -> publish`. no base anchor is renamed, skipped, or reordered; `prepare` stays inherited. | stage | gstack behavior | route | | --- | --- | --- | | `requirements` | office-hours intake: demand, status quo, user specificity, narrow wedge, observation, future fit | `gstack.office-hours` | | `plan` | autoplan draft | `gstack.founder-reviewer` | | `plan-review` | `gstack-plan-review` fanout: founder scope, design, engineering, developer-experience lanes | `gstack.review-synthesizer` | | `decompose` | implementation convoy creation | `gstack.decomposer` | | `implement` / `implement-same-session` | drains `gstack-work` (separate) or `gstack-work-item` (same-session) | `{implementation_target}` | | `review` | `gstack-code-review` fanout: staff, qa-evidence, cso-security, gap-analysis lanes | `gstack.review-synthesizer` | | `qa` (pack-added) | `gstack-qa-review` fanout: browser qa and regression-test evidence | `gstack.qa-lead` | | `release-readiness` (pack-added) | `gstack-release-readiness` fanout: documentation, ship readiness, deployment readiness | `gstack.release-engineer` | | `finalize` | sprint report under the artifact root | `gstack.release-engineer` | | `publish` | optional push / pr lane | `gc.publisher` | `qa` and `release-readiness` are the only pack-added steps, anchored after `review` and before `finalize`: `qa` needs `review`, `release-readiness` needs `qa`, `finalize` is rewired to need `release-readiness`, and `publish` still needs `finalize`. each expands a check-gated loop (`implementation-review-approved.sh`; qa allows 6 attempts, release-readiness 4) whose final lane (`synthesize-qa`, `synthesize-release-readiness`) owns the `code_review.verdict=done|iterate` loop verdict. their outputs are the approved qa summary recorded on the workflow root at `gc.build.qa_summary_path` before release readiness begins and the approved readiness summary at `gc.build.release_readiness_summary_path` before finalize begins. the `review` anchor works the same way: `gstack-code-review` records the review context, fans out the staff, qa-evidence, cso-security, and gap-analysis lanes, fans in at `synthesize-code-review`, and loops an `apply-review-findings` lane (routed to the caller-selected implementation target) through a bounded graph check until `code_review.verdict=done` lands on the workflow root. `gstack-fix-loop` carries the same review-fix contract for standalone adapter use. supported modes and drain policies, as declared in `[metadata.gc.methodology]` of `gstack-build`: - `interaction_modes`: `interactive`, `autonomous`, `headless` (inherited `interaction_mode` var; the pack pins the default to `interactive` because raw gstack is conversation-heavy) - `review_modes`: `report`, `agent`, `interactive` (inherited `review_mode` var; the pack pins the default to `interactive` to match the office-hours posture) - `implementation_strategy`: `drain` with `allowed_drain_policies` of `separate` (drains `gstack-work` item formulas with exclusive member access) and `same-session` (drains `gstack-work-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`) the native stage formulas extend the matching base methodology contracts: `gstack-planning` (`planning-base`), `gstack-decomposition` (`decomposition-base`), `gstack-implementation` (`implement`), `gstack-review` (`code-review-base`), and `gstack-fix-loop` (`fix-loop-base`), plus the drain formulas `gstack-work` (`do-work`) and `gstack-work-item` (`do-work-item`). ## customization pass any of these as `--var k=v` on the launch command. the selector vars also let shared gas city adapters pick gstack stages without using `gstack-build`. | variable | default | what it changes | | --- | --- | --- | | `interaction_mode` | `interactive` | human participation in planning and gates. `interactive` preserves blocking questions and approval menus; `autonomous` records assumptions instead of asking; `headless` never blocks. note: gstack defaults this to `interactive`, unlike the other methodology packs. | | `review_mode` | `interactive` | review authority. `report` writes findings only, without applying fixes or opening release paths; `agent` is a structured machine handoff whose fixes are applied by the loop; `interactive` preserves a raw top-level review that may apply safe fixes. also defaulted to `interactive` here. | | `drain_policy` | `separate` | `separate` runs each implementation bead in its own worktree lane in parallel by convoy dependencies; `same-session` runs all items in one shared single-lane session. | | `implementation_target` | `gstack.implementer` | the role that implements drained work items and applies review/qa fix findings. | | `artifact_root` | (required input) | directory under the rig where all stage artifacts land. | | `push` | `false` | allow the publish stage to push after all checks pass. | | `open_pr` | `false` | allow the publish stage to open a pr after all checks pass. | | `max_iterations` | `10` | maximum implementation/review fix attempts. | | `planning_formula` | `gstack-planning` | selector: planning methodology formula. | | `decomposition_formula` | `gstack-decomposition` | selector: decomposition methodology formula. | | `implementation_formula` | `gstack-implementation` | selector: implementation entry formula. | | `implementation_item_formula` | `gstack-work-item` | selector: single-item implementation formula for shared drains. | | `code_review_formula` | `gstack-review` | selector: code-review methodology formula. | | `review_fix_formula` | `gstack-fix-loop` | selector: review-fix loop formula. | step prompt text is also customizable without touching the formula graph. each stage reads its body from `assets/workflows/gstack-build/.md`, and a file at the same relative path in a higher-priority city or local pack layer shadows it. for example, to tighten the qa gate, create `assets/workflows/gstack-build/qa.md` in your city assets: ```markdown run gstack qa against the staging deployment, not a local build. - exercise the changed user flow end to end in a real browser session. - attach the regression test command output for every changed package. - treat missing reproduction steps for any found defect as iterate, not done. ``` for the full customization contract (basic asset shadowing and advanced step override rules), see \"stable workflow override interface\" in the [gascity pack readme](../gascity/readme.md). ## examples interactive sprint on a new feature (all defaults: interactive intake and review, separate drain): ```sh gc bd create \"add csv export to the reports page\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/csv-export/build \\ --var drain_policy=separate ``` autonomous run that pushes and opens a pr — overrides both interactive defaults so no lane blocks on a human, and enables the publish side effects: ```sh gc bd create \"migrate the settings page to the v2 form components\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/settings-v2/build \\ --var drain_policy=separate \\ --var interaction_mode=autonomous \\ --var review_mode=agent \\ --var push=true \\ --var open_pr=true ``` same-session drain for a small change — keeps every implementation item in one shared worktree and conversation instead of parallel lanes: ```sh gc bd create \"fix the off-by-one in pagination page counts\" gc sling gc.run-operator --on gstack-build \\ --var artifact_root=plans/pagination-fix/build \\ --var drain_policy=same-session ``` ## what's vendored the vendored upstream files under `vendor/gstack` are reference material; `vendor/gstack/upstream.toml` records the upstream source repository, the pinned commit, and the mit license. the installed files under `skills/` (office-hours, autoplan, plan-eng-review, review, investigate, document-release) expose the same vocabulary for agents. runtime execution is owned by formulas, beads, and gas city graph lanes. raw-framework subagents become gas city fanouts. do not preserve upstream subagent behavior as provider-native subagents; model that work as formulas or expansion children. every lane in this pack routes through `gc.run_target` to a providerless `gstack.*` agent, a `gc.*` role, or the caller-selected implementation target — no lane dispatches provider-native subagents. ## compatibility ledger the pack-local compatibility ledger lives at [`gstack/requirements.md`](./requirements.md) and records the build-base contract proofs — the inherited `gc` import, the preserved anchor order, the qa/release-readiness insertion between review and finalize — together with the evidence commands that reproduce each claim. 0.1.0 first registry release of the gstack build factory. 0.1.1 implementer commits in the source-anchor worktree; code review records a validated review report on the workflow root; apply-findings keeps artifacts schema-valid and the plan is re-validated after plan review; picks up newer gascity pack commits (do-work now needs an origin remote).", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/gstack/README.md", "content": "# gstack Gas City Pack\n\nThis pack adapts the [garrytan/gstack](https://github.com/garrytan/gstack)\nmethodology — a founder-style sprint that runs YC-office-hours intake,\nmulti-perspective plan review, staff code review, QA, security review, and\nrelease readiness — into durable Gas City workflows. You get one full-lifecycle\nformula, `gstack-build`, that keeps the recognizable garrytan/gstack sprint:\n\nThink -> Plan -> Build -> Review -> Test -> Ship -> Reflect\n\nUpstream gstack is a Claude Code skills pack whose roles (YC Office Hours,\nCEO/founder review, engineering review, design review, staff review, QA, CSO\nsecurity, documentation, release engineering) hand work to each other through\nsubagents. In this pack those roles are providerless Gas City agents, and their\nmulti-agent handoffs are Gas City fanouts: persistent, observable, and\nretryable through the workflow graph.\n\n## When to choose gstack\n\n- You want the strictest pre-ship gating of the methodology packs: gstack adds\n dedicated `qa` and `release-readiness` stages between code review and\n finalize, so nothing finalizes without browser-oriented QA evidence and a\n documentation/ship/deploy readiness pass.\n- You want founder- and PM-flavored review perspectives. Plan review fans out\n CEO/founder scope, design, engineering, and developer-experience lanes;\n requirements come from an office-hours interrogation (demand, status quo,\n user specificity, narrow wedge) rather than a plain requirements template.\n- You are comfortable answering questions during the run. Unlike `build-basic`\n and the other methodology packs, gstack defaults both `interaction_mode` and\n `review_mode` to `interactive`, because raw gstack is intentionally\n conversation-heavy. You can override both for automation.\n- If you want a leaner run, pick `build-basic` (single-lane review, no extra\n gates) or another methodology pack (Compound Engineering, Superpowers, BMAD);\n they share the same `build-base` contract, so switching later is a one-line\n formula change.\n\n## Quick start\n\nThese steps go from a fresh machine to a completed gstack sprint.\n\n1. Install Gas City and create a city:\n\n ```sh\n brew install gascity\n gc init ~/my-city && cd ~/my-city && gc start\n ```\n\n2. Add the repository you want to build in as a rig:\n\n ```sh\n mkdir proj && cd proj && git init && gc rig add .\n ```\n\n3. Import this pack at city scope. From the city directory — this writes the\n import, fetches the latest release, and pins it in `packs.lock`, no clone\n needed:\n\n ```sh\n gc import add https://github.com/gastownhall/gascity-packs.git//gstack\n ```\n\n Then import the Gas City roles pack on the rig: the methodology pack\n supplies the formulas and `gstack.*` agents, and the rig-level\n `gascity/roles` import supplies the `gc.*` role agents (run operator,\n publisher, implementation workers) that the formulas route to. In your\n `city.toml`, then `gc import install`:\n\n ```toml\n [[rigs]]\n name = \"proj\"\n\n [rigs.imports.gc]\n source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\"\n ```\n\n Contributors working on the packs themselves can clone\n `https://github.com/gastownhall/gascity-packs` and point either `source`\n at the local path (for example `../gascity-packs/gstack`) instead.\n\n4. Create a bead for the goal and sling it onto `gstack-build`. The formula\n declares `target_required = true`, so it is launched against a bead (or\n convoy), not with `--formula`:\n\n ```sh\n gc bd create \"Add CSV export to the reports page\"\n gc sling gc.run-operator --on gstack-build \\\n --var artifact_root=plans/csv-export/build \\\n --var drain_policy=separate\n ```\n\n5. The run walks the build-base stage sequence. With gstack's overrides and\n additions, that means: `prepare`, office-hours `requirements`, autoplan\n `plan`, the `plan-review` fanout (founder scope, design, engineering,\n developer experience), `decompose` into an implementation convoy,\n `implement` (a drain of `gstack-work` items), the `review` fanout (staff,\n QA evidence, CSO security, gap analysis), then the two pack-added gates —\n `qa` (browser QA plus regression-test evidence) and `release-readiness`\n (documentation, ship readiness, deployment readiness) — before `finalize`\n writes the sprint report and `publish` optionally pushes or opens a PR.\n In the default interactive modes, expect the office-hours and review lanes\n to ask you questions before proceeding.\n\n6. Artifacts land under your `artifact_root` (here\n `plans/csv-export/build/` in the rig): requirements, plan, decomposition,\n review report, QA summary, release-readiness summary, and the final sprint\n report. Inspect the formula surface at any time with:\n\n ```sh\n gc formula catalog --json\n gc formula show gstack-build --json\n ```\n\n## Stage map\n\n`gstack-build` extends `build-base` and keeps the inherited anchor order\n`prepare -> requirements -> plan -> plan-review -> decompose ->\nimplement/implement-same-session -> review -> finalize -> publish`. No base\nanchor is renamed, skipped, or reordered; `prepare` stays inherited.\n\n| Stage | gstack behavior | Route |\n| --- | --- | --- |\n| `requirements` | Office-hours intake: demand, status quo, user specificity, narrow wedge, observation, future fit | `gstack.office-hours` |\n| `plan` | Autoplan draft | `gstack.founder-reviewer` |\n| `plan-review` | `gstack-plan-review` fanout: founder scope, design, engineering, developer-experience lanes | `gstack.review-synthesizer` |\n| `decompose` | Implementation convoy creation | `gstack.decomposer` |\n| `implement` / `implement-same-session` | Drains `gstack-work` (separate) or `gstack-work-item` (same-session) | `{implementation_target}` |\n| `review` | `gstack-code-review` fanout: staff, QA-evidence, CSO-security, gap-analysis lanes | `gstack.review-synthesizer` |\n| `qa` (pack-added) | `gstack-qa-review` fanout: browser QA and regression-test evidence | `gstack.qa-lead` |\n| `release-readiness` (pack-added) | `gstack-release-readiness` fanout: documentation, ship readiness, deployment readiness | `gstack.release-engineer` |\n| `finalize` | Sprint report under the artifact root | `gstack.release-engineer` |\n| `publish` | Optional push / PR lane | `gc.publisher` |\n\n`qa` and `release-readiness` are the only pack-added steps, anchored after\n`review` and before `finalize`: `qa` needs `review`, `release-readiness` needs\n`qa`, `finalize` is rewired to need `release-readiness`, and `publish` still\nneeds `finalize`. Each expands a check-gated loop\n(`implementation-review-approved.sh`; QA allows 6 attempts, release-readiness\n4) whose final lane (`synthesize-qa`, `synthesize-release-readiness`) owns the\n`code_review.verdict=done|iterate` loop verdict. Their outputs are the\napproved QA summary recorded on the workflow root at\n`gc.build.qa_summary_path` before release readiness begins and the approved\nreadiness summary at `gc.build.release_readiness_summary_path` before finalize\nbegins.\n\nThe `review` anchor works the same way: `gstack-code-review` records the\nreview context, fans out the staff, QA-evidence, CSO-security, and\ngap-analysis lanes, fans in at `synthesize-code-review`, and loops an\n`apply-review-findings` lane (routed to the caller-selected implementation\ntarget) through a bounded graph check until `code_review.verdict=done` lands\non the workflow root. `gstack-fix-loop` carries the same review-fix contract\nfor standalone adapter use.\n\nSupported modes and drain policies, as declared in\n`[metadata.gc.methodology]` of `gstack-build`:\n\n- `interaction_modes`: `interactive`, `autonomous`, `headless` (inherited\n `interaction_mode` var; the pack pins the default to `interactive` because\n raw gstack is conversation-heavy)\n- `review_modes`: `report`, `agent`, `interactive` (inherited `review_mode`\n var; the pack pins the default to `interactive` to match the office-hours\n posture)\n- `implementation_strategy`: `drain` with `allowed_drain_policies` of\n `separate` (drains `gstack-work` item formulas with exclusive member access)\n and `same-session` (drains `gstack-work-item` in one shared single-lane\n session with `on_item_failure = \"skip_remaining\"`)\n\nThe native stage formulas extend the matching base methodology contracts:\n`gstack-planning` (`planning-base`), `gstack-decomposition`\n(`decomposition-base`), `gstack-implementation` (`implement`), `gstack-review`\n(`code-review-base`), and `gstack-fix-loop` (`fix-loop-base`), plus the drain\nformulas `gstack-work` (`do-work`) and `gstack-work-item` (`do-work-item`).\n\n## Customization\n\nPass any of these as `--var k=v` on the launch command. The selector vars also\nlet shared Gas City adapters pick gstack stages without using `gstack-build`.\n\n| Variable | Default | What it changes |\n| --- | --- | --- |\n| `interaction_mode` | `interactive` | Human participation in planning and gates. `interactive` preserves blocking questions and approval menus; `autonomous` records assumptions instead of asking; `headless` never blocks. Note: gstack defaults this to `interactive`, unlike the other methodology packs. |\n| `review_mode` | `interactive` | Review authority. `report` writes findings only, without applying fixes or opening release paths; `agent` is a structured machine handoff whose fixes are applied by the loop; `interactive` preserves a raw top-level review that may apply safe fixes. Also defaulted to `interactive` here. |\n| `drain_policy` | `separate` | `separate` runs each implementation bead in its own worktree lane in parallel by convoy dependencies; `same-session` runs all items in one shared single-lane session. |\n| `implementation_target` | `gstack.implementer` | The role that implements drained work items and applies review/QA fix findings. |\n| `artifact_root` | (required input) | Directory under the rig where all stage artifacts land. |\n| `push` | `false` | Allow the publish stage to push after all checks pass. |\n| `open_pr` | `false` | Allow the publish stage to open a PR after all checks pass. |\n| `max_iterations` | `10` | Maximum implementation/review fix attempts. |\n| `planning_formula` | `gstack-planning` | Selector: planning methodology formula. |\n| `decomposition_formula` | `gstack-decomposition` | Selector: decomposition methodology formula. |\n| `implementation_formula` | `gstack-implementation` | Selector: implementation entry formula. |\n| `implementation_item_formula` | `gstack-work-item` | Selector: single-item implementation formula for shared drains. |\n| `code_review_formula` | `gstack-review` | Selector: code-review methodology formula. |\n| `review_fix_formula` | `gstack-fix-loop` | Selector: review-fix loop formula. |\n\nStep prompt text is also customizable without touching the formula graph. Each\nstage reads its body from `assets/workflows/gstack-build/.md`, and a\nfile at the same relative path in a higher-priority city or local pack layer\nshadows it. For example, to tighten the QA gate, create\n`assets/workflows/gstack-build/qa.md` in your city assets:\n\n```markdown\nRun gstack QA against the staging deployment, not a local build.\n\n- Exercise the changed user flow end to end in a real browser session.\n- Attach the regression test command output for every changed package.\n- Treat missing reproduction steps for any found defect as iterate, not done.\n```\n\nFor the full customization contract (basic asset shadowing and advanced step\noverride rules), see \"Stable Workflow Override Interface\" in the\n[gascity pack README](../gascity/README.md).\n\n## Examples\n\nInteractive sprint on a new feature (all defaults: interactive intake and\nreview, separate drain):\n\n```sh\ngc bd create \"Add CSV export to the reports page\"\ngc sling gc.run-operator --on gstack-build \\\n --var artifact_root=plans/csv-export/build \\\n --var drain_policy=separate\n```\n\nAutonomous run that pushes and opens a PR — overrides both interactive\ndefaults so no lane blocks on a human, and enables the publish side effects:\n\n```sh\ngc bd create \"Migrate the settings page to the v2 form components\"\ngc sling gc.run-operator --on gstack-build \\\n --var artifact_root=plans/settings-v2/build \\\n --var drain_policy=separate \\\n --var interaction_mode=autonomous \\\n --var review_mode=agent \\\n --var push=true \\\n --var open_pr=true\n```\n\nSame-session drain for a small change — keeps every implementation item in one\nshared worktree and conversation instead of parallel lanes:\n\n```sh\ngc bd create \"Fix the off-by-one in pagination page counts\"\ngc sling gc.run-operator --on gstack-build \\\n --var artifact_root=plans/pagination-fix/build \\\n --var drain_policy=same-session\n```\n\n## What's vendored\n\nThe vendored upstream files under `vendor/gstack` are reference material;\n`vendor/gstack/upstream.toml` records the upstream source repository, the\npinned commit, and the MIT license. The installed files under `skills/`\n(office-hours, autoplan, plan-eng-review, review, investigate,\ndocument-release) expose the same vocabulary for agents. Runtime execution is\nowned by formulas, beads, and Gas City graph lanes.\n\nRaw-framework subagents become Gas City fanouts. Do not preserve upstream\nsubagent behavior as provider-native subagents; model that work as formulas or\nexpansion children. Every lane in this pack routes through `gc.run_target` to\na providerless `gstack.*` agent, a `gc.*` role, or the caller-selected\nimplementation target — no lane dispatches provider-native subagents.\n\n## Compatibility ledger\n\nThe pack-local compatibility ledger lives at\n[`gstack/REQUIREMENTS.md`](./REQUIREMENTS.md) and records the build-base\ncontract proofs — the inherited `gc` import, the preserved anchor order, the\nqa/release-readiness insertion between review and finalize — together with the\nevidence commands that reproduce each claim." }, "og_image": "/og/gstack.svg", - "latest": "0.1.0", + "latest": "0.1.1", "releases": [ { "version": "0.1.0", @@ -377,6 +377,14 @@ "hash": "sha256:4f180f1327933239a29918e661c0cd3a54ff3de84ab981324665e776de2eff8f", "description": "First registry release of the gstack build factory.", "withdrawn": false + }, + { + "version": "0.1.1", + "ref": "main", + "commit": "390cac1a8a0478b6dfde603dae00a43ed9748d13", + "hash": "sha256:ed74c2b288875a5f563d2b0d2a5afffab048a4136b237fb94d6e7baf60031cba", + "description": "Implementer commits in the source-anchor worktree; code review records a validated review report on the workflow root; apply-findings keeps artifacts schema-valid and the plan is re-validated after plan review; picks up newer gascity pack commits (do-work now needs an origin remote).", + "withdrawn": false } ] }, @@ -389,10 +397,10 @@ "description": "Disaggregates project-level work scoping out of the mayor — adds one always-on, rig-scoped project-lead per rig that triages its rig and dispatches its own ready work directly, with deterministic severity-based escalation (no relay agent). Composes with any slack adapter pack; ships only the oversight role and its escalation machinery, not a slack bridge.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/oversight-rig", "source_kind": "git", - "search_text": "oversight-rig gascity-packs maintained gas city disaggregates project-level work scoping out of the mayor — adds one always-on, rig-scoped project-lead per rig that triages its rig and dispatches its own ready work directly, with deterministic severity-based escalation (no relay agent). composes with any slack adapter pack; ships only the oversight role and its escalation machinery, not a slack bridge. https://github.com/gastownhall/gascity-packs/tree/main/oversight-rig git # oversight-rig a drop-in gas city pack that **disaggregates project-level work scoping out of the mayor**. it adds one always-on, rig-scoped `project-lead` role per rig, so the mayor stops being the bottleneck for every project's day-to-day triage and dispatch. ## the problem it solves in a single-mayor city, the mayor ends up in the minutiae of every project's work scoping — what to dispatch, what's blocked, what to escalate — across every rig at once. that doesn't scale. ## the model ``` mayor (city, unchanged) ──── plans cross-cutting work, handles escalations rig: foo rig: bar … project-lead-foo project-lead-bar • bounded to foo's beads • bounded to bar's beads • reads foo/.gc/ • reads bar/.gc/ project-brief.md project-brief.md • dispatches its own • dispatches its own ready work directly ready work directly ``` - **`project-lead`** (rig scope, always-on, one per rig) - bounded to a single rig's beads. reads its persona, current focus, and escalation triggers from `/.gc/project-brief.md` — each project owns its own scoping context, so it never piles onto the mayor. - triages its rig every tick and **dispatches ready, in-scope work in its own rig directly** — it does not route every dispatch through the mayor. - judges severity (info vs escalate) and writes structured rollup beads. - **the mayor stays unchanged** and shrinks to what is genuinely cross-cutting: planning work that spans rigs, and reacting to escalations. ## deterministic escalation (no relay agent) the project-lead writes a rollup bead labeled `severity:escalate`. a scheduled order (`escalate-rollups`) uses a mechanical condition trigger that checks for undelivered escalate-severity rollups and delivers them via extmsg — **no second agent decides \"is this escalation-worthy.\"** the judgment is made once, by the agent with the right context, and the rollup bead is the audit trail. human replies route straight back to the bound project-lead. ## what's in the pack - `agents/project-lead/` — the role (agent config, prompt template, and a `project-brief.template.md` to copy per rig) - `orders/` — `patrol-project-leads` (triage cadence) and `escalate-rollups` (deterministic delivery) - `assets/scripts/` — the delivery script and a rig→channel resolver ## requirements - an extmsg/slack adapter in the city for outbound delivery and inbound replies — **compose this pack with your slack pack** (e.g. `slack-full`, `slack-channel`, or `slack-mini`). this pack ships only the oversight role and its escalation machinery, not a slack bridge. - optional: the project-lead's rig-scoped dispatch examples use convoy formulas (`mol-decompose`, `mol-pr-from-issue`) supplied by a workflow pack (e.g. `gastown`). the role works without them. ## install 1. add the pack to your `city.toml` and stamp a `project-lead` per rig. 2. for each rig under oversight, author its brief: ```bash cp agents/project-lead/project-brief.template.md /.gc/project-brief.md # then edit the brief to fit the project ``` 3. bind each rig's `project-lead` session to that rig's channel via your slack pack so escalations deliver and replies route back. 4. `gc supervisor reload` — picks up the new agents and orders. 0.1.0 initial oversight-rig release: rig-scoped project-lead + deterministic escalation machinery.", + "search_text": "oversight-rig gascity-packs maintained gas city disaggregates project-level work scoping out of the mayor — adds one always-on, rig-scoped project-lead per rig that triages its rig and dispatches its own ready work directly, with deterministic severity-based escalation (no relay agent). composes with any slack adapter pack; ships only the oversight role and its escalation machinery, not a slack bridge. https://github.com/gastownhall/gascity-packs/tree/main/oversight-rig git # oversight-rig a drop-in gas city pack that **disaggregates project-level work scoping out of the mayor**. it adds one always-on, rig-scoped `project-lead` role per rig, so the mayor stops being the bottleneck for every project's day-to-day triage and dispatch. ## the problem it solves in a single-mayor city, the mayor ends up in the minutiae of every project's work scoping — what to dispatch, what's blocked, what to escalate — across every rig at once. that doesn't scale. ## the model ``` mayor (city, unchanged) ──── plans cross-cutting work, handles escalations rig: foo rig: bar … project-lead-foo project-lead-bar • bounded to foo's beads • bounded to bar's beads • reads foo/.gc/ • reads bar/.gc/ project-brief.md project-brief.md • dispatches its own • dispatches its own ready work directly ready work directly ``` - **`project-lead`** (rig scope, always-on, one per rig) - bounded to a single rig's beads. reads its persona, current focus, and escalation triggers from `/.gc/project-brief.md` — each project owns its own scoping context, so it never piles onto the mayor. - triages its rig every tick and **dispatches ready, in-scope work in its own rig directly** — it does not route every dispatch through the mayor. - judges severity (info vs escalate) and writes structured rollup beads. - **the mayor stays unchanged** and shrinks to what is genuinely cross-cutting: planning work that spans rigs, and reacting to escalations. ## deterministic escalation (no relay agent) the project-lead writes a rollup bead labeled `severity:escalate`. a scheduled order (`escalate-rollups`) uses a mechanical condition trigger that checks for undelivered escalate-severity rollups and delivers them via extmsg — **no second agent decides \"is this escalation-worthy.\"** the judgment is made once, by the agent with the right context, and the rollup bead is the audit trail. human replies route straight back to the bound project-lead. ## what's in the pack - `agents/project-lead/` — the role (agent config, prompt template, and a `project-brief.template.md` to copy per rig) - `orders/` — `patrol-project-leads` (triage cadence) and `escalate-rollups` (deterministic delivery) - `assets/scripts/` — the delivery script and a rig→channel resolver - `skills/city-executive-status/` — an optional workflow for maintaining an obsidian-compatible portfolio brief from project-owner updates ## optional executive status brief the `city-executive-status` skill packages the shareable workflow for requesting structured project-owner updates, validating them, and writing one concise portfolio brief. it can optionally publish a content-hash-deduplicated summary through a deployment-specific adapter. importing this pack exposes the skill but does not activate its example schedules or write to a vault. the only active pack orders remain `patrol-project-leads` and `escalate-rollups`. to enable the workflow, follow the skill's `skill.md`, copy its environment and order examples into the consuming city, configure local paths, and verify dry-run output before scheduling writes. ## requirements - an extmsg/slack adapter in the city for outbound delivery and inbound replies — **compose this pack with your slack pack** (e.g. `slack-full`, `slack-channel`, or `slack-mini`). this pack ships only the oversight role and its escalation machinery, not a slack bridge. - optional: the project-lead's rig-scoped dispatch examples use convoy formulas (`mol-decompose`, `mol-pr-from-issue`) supplied by a workflow pack (e.g. `gastown`). the role works without them. - optional: the executive-status skill needs python 3.11 or newer. obsidian and a publishing adapter are not required; without them it writes ordinary markdown to a configured path. ## install 1. add the pack to your `city.toml` and stamp a `project-lead` per rig. 2. for each rig under oversight, author its brief: ```bash cp agents/project-lead/project-brief.template.md /.gc/project-brief.md # then edit the brief to fit the project ``` 3. bind each rig's `project-lead` session to that rig's channel via your slack pack so escalations deliver and replies route back. 4. `gc supervisor reload` — picks up the new agents and orders. 0.1.0 initial oversight-rig release: rig-scoped project-lead + deterministic escalation machinery.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/oversight-rig/README.md", - "content": "# oversight-rig\n\nA drop-in Gas City pack that **disaggregates project-level work scoping out of the mayor**. It adds one always-on, rig-scoped `project-lead` role per rig, so the mayor stops being the bottleneck for every project's day-to-day triage and dispatch.\n\n## The problem it solves\n\nIn a single-mayor city, the mayor ends up in the minutiae of every project's work scoping — what to dispatch, what's blocked, what to escalate — across every rig at once. That doesn't scale.\n\n## The model\n\n```\nmayor (city, unchanged) ──── plans cross-cutting work, handles escalations\n\n rig: foo rig: bar …\n project-lead-foo project-lead-bar\n • bounded to foo's beads • bounded to bar's beads\n • reads foo/.gc/ • reads bar/.gc/\n project-brief.md project-brief.md\n • dispatches its own • dispatches its own\n ready work directly ready work directly\n```\n\n- **`project-lead`** (rig scope, always-on, one per rig)\n - Bounded to a single rig's beads. Reads its persona, current focus, and escalation triggers from `/.gc/project-brief.md` — each project owns its own scoping context, so it never piles onto the mayor.\n - Triages its rig every tick and **dispatches ready, in-scope work in its own rig directly** — it does not route every dispatch through the mayor.\n - Judges severity (info vs escalate) and writes structured rollup beads.\n- **The mayor stays unchanged** and shrinks to what is genuinely cross-cutting: planning work that spans rigs, and reacting to escalations.\n\n## Deterministic escalation (no relay agent)\n\nThe project-lead writes a rollup bead labeled `severity:escalate`. A scheduled order (`escalate-rollups`) uses a mechanical condition trigger that checks for undelivered escalate-severity rollups and delivers them via extmsg — **no second agent decides \"is this escalation-worthy.\"** The judgment is made once, by the agent with the right context, and the rollup bead is the audit trail. Human replies route straight back to the bound project-lead.\n\n## What's in the pack\n\n- `agents/project-lead/` — the role (agent config, prompt template, and a `project-brief.template.md` to copy per rig)\n- `orders/` — `patrol-project-leads` (triage cadence) and `escalate-rollups` (deterministic delivery)\n- `assets/scripts/` — the delivery script and a rig→channel resolver\n\n## Requirements\n\n- An extmsg/slack adapter in the city for outbound delivery and inbound replies — **compose this pack with your slack pack** (e.g. `slack-full`, `slack-channel`, or `slack-mini`). This pack ships only the oversight role and its escalation machinery, not a slack bridge.\n- Optional: the project-lead's rig-scoped dispatch examples use convoy formulas (`mol-decompose`, `mol-pr-from-issue`) supplied by a workflow pack (e.g. `gastown`). The role works without them.\n\n## Install\n\n1. Add the pack to your `city.toml` and stamp a `project-lead` per rig.\n2. For each rig under oversight, author its brief:\n ```bash\n cp agents/project-lead/project-brief.template.md /.gc/project-brief.md\n # then edit the brief to fit the project\n ```\n3. Bind each rig's `project-lead` session to that rig's channel via your slack pack so escalations deliver and replies route back.\n4. `gc supervisor reload` — picks up the new agents and orders." + "content": "# oversight-rig\n\nA drop-in Gas City pack that **disaggregates project-level work scoping out of the mayor**. It adds one always-on, rig-scoped `project-lead` role per rig, so the mayor stops being the bottleneck for every project's day-to-day triage and dispatch.\n\n## The problem it solves\n\nIn a single-mayor city, the mayor ends up in the minutiae of every project's work scoping — what to dispatch, what's blocked, what to escalate — across every rig at once. That doesn't scale.\n\n## The model\n\n```\nmayor (city, unchanged) ──── plans cross-cutting work, handles escalations\n\n rig: foo rig: bar …\n project-lead-foo project-lead-bar\n • bounded to foo's beads • bounded to bar's beads\n • reads foo/.gc/ • reads bar/.gc/\n project-brief.md project-brief.md\n • dispatches its own • dispatches its own\n ready work directly ready work directly\n```\n\n- **`project-lead`** (rig scope, always-on, one per rig)\n - Bounded to a single rig's beads. Reads its persona, current focus, and escalation triggers from `/.gc/project-brief.md` — each project owns its own scoping context, so it never piles onto the mayor.\n - Triages its rig every tick and **dispatches ready, in-scope work in its own rig directly** — it does not route every dispatch through the mayor.\n - Judges severity (info vs escalate) and writes structured rollup beads.\n- **The mayor stays unchanged** and shrinks to what is genuinely cross-cutting: planning work that spans rigs, and reacting to escalations.\n\n## Deterministic escalation (no relay agent)\n\nThe project-lead writes a rollup bead labeled `severity:escalate`. A scheduled order (`escalate-rollups`) uses a mechanical condition trigger that checks for undelivered escalate-severity rollups and delivers them via extmsg — **no second agent decides \"is this escalation-worthy.\"** The judgment is made once, by the agent with the right context, and the rollup bead is the audit trail. Human replies route straight back to the bound project-lead.\n\n## What's in the pack\n\n- `agents/project-lead/` — the role (agent config, prompt template, and a `project-brief.template.md` to copy per rig)\n- `orders/` — `patrol-project-leads` (triage cadence) and `escalate-rollups` (deterministic delivery)\n- `assets/scripts/` — the delivery script and a rig→channel resolver\n- `skills/city-executive-status/` — an optional workflow for maintaining an Obsidian-compatible portfolio brief from project-owner updates\n\n## Optional executive status brief\n\nThe `city-executive-status` skill packages the shareable workflow for requesting\nstructured project-owner updates, validating them, and writing one concise\nportfolio brief. It can optionally publish a content-hash-deduplicated summary\nthrough a deployment-specific adapter.\n\nImporting this pack exposes the skill but does not activate its example\nschedules or write to a vault. The only active pack orders remain\n`patrol-project-leads` and `escalate-rollups`. To enable the workflow, follow the\nskill's `SKILL.md`, copy its environment and order examples into the consuming\ncity, configure local paths, and verify dry-run output before scheduling writes.\n\n## Requirements\n\n- An extmsg/slack adapter in the city for outbound delivery and inbound replies — **compose this pack with your slack pack** (e.g. `slack-full`, `slack-channel`, or `slack-mini`). This pack ships only the oversight role and its escalation machinery, not a slack bridge.\n- Optional: the project-lead's rig-scoped dispatch examples use convoy formulas (`mol-decompose`, `mol-pr-from-issue`) supplied by a workflow pack (e.g. `gastown`). The role works without them.\n- Optional: the executive-status skill needs Python 3.11 or newer. Obsidian and a publishing adapter are not required; without them it writes ordinary Markdown to a configured path.\n\n## Install\n\n1. Add the pack to your `city.toml` and stamp a `project-lead` per rig.\n2. For each rig under oversight, author its brief:\n ```bash\n cp agents/project-lead/project-brief.template.md /.gc/project-brief.md\n # then edit the brief to fit the project\n ```\n3. Bind each rig's `project-lead` session to that rig's channel via your slack pack so escalations deliver and replies route back.\n4. `gc supervisor reload` — picks up the new agents and orders." }, "og_image": "/og/oversight-rig.svg", "latest": "0.1.0", @@ -416,10 +424,10 @@ "description": "Author-side PR discipline as a pack: planning, blast-radius mapping, outgoing-PR scorecard review, and pre-push gating (mol-pr-start / mol-pr-blast-radius / mol-pr-review / mol-pr-ship).", "source": "https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline", "source_kind": "git", - "search_text": "pr-pipeline gascity-packs maintained gas city author-side pr discipline as a pack: planning, blast-radius mapping, outgoing-pr scorecard review, and pre-push gating (mol-pr-start / mol-pr-blast-radius / mol-pr-review / mol-pr-ship). https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline git # pr pipeline author-side and review-side pr discipline distributed as a gas city pack. encodes the planning, blast-radius, scorecard-review, and pre-push gating workflows that careful contributors run by hand, so any city that imports this pack gets the same discipline as platform-native formulas and commands. ## status **v0.1.0** — initial release. ships six formulas: four with matching wrapper commands, plus two dispatched on demand. the four wrapper-command formulas: | formula | command | purpose | |---------|---------|---------| | `mol-pr-start` | `gc pr plan ` | issue → structured plan with blocking gates | | `mol-pr-blast-radius` | `gc pr blast-radius \"\"` | map impact surface (callers, configs, concurrency) | | `mol-pr-review` | `gc pr review ` | outgoing-pr self-review against an 11-category scorecard | | `mol-pr-ship` | `gc pr ship` | pre-push gate (simplify → review-iterate → contributor check); stops at report | all four wrapper-command formulas are read-only by default for filesystem changes outside their own output paths. `mol-pr-ship` may modify the diff during simplify and review-iteration stages; everything else writes only to `.gc/pr-pipeline//`. none of them push or open prs — those decisions stay with the caller. two further formulas ship without a wrapper command — they are dispatched on demand (typically by a maintenance pl) rather than run by hand: | formula | dispatch | purpose | |---------|----------|---------| | `mol-pr-triage` | `gc sling / mol-pr-triage --formula` | scan/classify open issues into a ranked work-queue | | `mol-pr-from-issue` | `gc sling / mol-pr-from-issue --formula --var issue_number=` | author-side macro chain: issue → plan → implement → ship gate → (optional) open pr | `mol-pr-from-issue` is slung as a routed molecule, so its github-issue input is named **`issue_number`** rather than `issue` — the bare `issue` var name is a reserved formulas-v2 alias for the routed work bead and would be clobbered. it defaults to halt-at-branch-ready (`auto_push` absent → no push, no pr); pass `--var auto_push=true` to authorize the eligibility-gated push. ## scope this pack is the **author side** — planning, building, and shipping the prs your city sends out. reviewing and merging **incoming** prs that arrive at your repo is a separate, maintainer-side concern and out of scope here. the one maintainer-side artifact shipped here is the adoption-review comment template (`templates/adoption-review-comment.md`); it reuses the same 11-category scorecard `mol-pr-review` applies, so review feedback keeps a consistent shape in both directions. ## usage in your city's `pack.toml`: ```toml [imports.pr-pipeline] source = \"../packs/pr-pipeline\" # path; or git url when published ``` ### plan a pr for an issue ```sh gc pr-pipeline pr plan 1234 --rig api-server ``` the formula reads the issue, runs blocking gates (competing-pr and architectural-refactor checks), maps blast radius, checks repo conventions, writes a structured plan to `.gc/pr-pipeline/plans/issue-1234.md`, and audits the plan against 19 recurring review findings. **no code is written.** ### map blast radius for a freeform scope ```sh gc pr-pipeline pr blast-radius \"funcxyz in pkg/foo\" --rig api-server ``` for changes that don't start from an issue — refactors, hotfixes, exploratory deltas — `mol-pr-blast-radius` is a standalone entry point with the same analysis shape the planner runs inline. ### self-review an outgoing pr ```sh gc pr-pipeline pr review 1234 --rig api-server ``` scorecard against 11 categories (behavioral correctness, contract fidelity, blast radius, concurrency, error handling, security, resource lifecycle, release safety, test evidence, architectural consistency, debuggability). pre-flags 7 recurring fixup themes. verdict: `block`, `request_changes`, or `approve`. ### run the pre-push gate ```sh gc pr-pipeline pr ship --rig api-server ``` four-stage pipeline: simplify → iterate an adversarial multi-reviewer panel (parallel reviewer subagents + adversarial synthesis) until clean → mechanical gates (build/vet/test/docs) → readiness report. **stops at the report.** push and pr-open are explicit caller actions this formula never performs. ### override the worker agent default agent for all wrappers is `polecat`. override with `--agent`: ```sh gc pr-pipeline pr plan 1234 --rig api-server --agent claude ``` or sling directly without the wrapper: ```sh gc sling api-server/polecat mol-pr-start --formula --var issue=1234 ``` ## pack contents ``` pr-pipeline/ ├── pack.toml ├── formulas/ │ ├── mol-pr-start.formula.toml 6-step planner │ ├── mol-pr-blast-radius.formula.toml 5-step impact mapper │ ├── mol-pr-review.formula.toml 4-step outgoing-pr scorecard │ ├── mol-pr-ship.formula.toml 5-step pre-push gate │ ├── mol-pr-triage.formula.toml issue scan → ranked work-queue │ └── mol-pr-from-issue.formula.toml 7-step issue → branch-ready pr ├── commands/ │ └── pr/ │ ├── plan/ (run.sh + help.md) │ ├── blast-radius/ │ ├── review/ │ └── ship/ └── templates/ └── adoption-review-comment.md canonical reviewer-side comment template (form 1: no maintainer changes; form 2: maintainer fixups present) ``` ### shared templates `templates/adoption-review-comment.md` is the canonical structure for the synthesis comment a maintainer posts when adopting a contributor pr. two forms cover all four merge paths a maintainer-side adoption workflow distinguishes (no maintainer changes vs. maintainer fixups present). the template documents: - the two literal comment shapes (form 1 / form 2) - all inputs the renderer needs (pr metadata, synthesis + scorecard from the review run, maintainer-fixup log/diff, final tip sha, model list, iteration count) - rendering rules per `{model-rendered}` field (the orchestration is mechanical; the prose is the model's job) - validation gates enforced before posting (verbatim opener, footer literal, section order, sha prefix match, iteration-count match) - a fail-stop fallback for malformed renders (write rejected text and stop — never post a partial comment) - path-specific adjustments for the four merge paths (a / b / c / d) the reviewer-side counterpart to this pack's `mol-pr-review` author-side scorecard. both produce structured findings against the same 11-category scorecard so contributors get consistent feedback shape regardless of direction. the full workflow for each formula lives in step descriptions. a coding agent (polecat or equivalent) follows them in sequence; gates can short-circuit with an early exit. ## why formula-shaped, not agent-as-directory this pack ships **formulas**, not standing agents. each formula is a bounded workflow (\"plan one pr, exit\" / \"score one pr, exit\") rather than a long-lived role like mayor or polecat. the consumer city's existing coding worker (whatever it's named) runs the formula — no extra agent deployment required. standing roles (mayor, polecat, witness, refinery) belong in their own packs as `agents//` directories. bounded workflows belong as `formulas/mol-.formula.toml` with the workflow inlined in step descriptions. 0.1.0 initial pr-pipeline release: four author-side formulas (mol-pr-start, mol-pr-blast-radius, mol-pr-review, mol-pr-ship) + wrapper commands.", + "search_text": "pr-pipeline gascity-packs maintained gas city author-side pr discipline as a pack: planning, blast-radius mapping, outgoing-pr scorecard review, and pre-push gating (mol-pr-start / mol-pr-blast-radius / mol-pr-review / mol-pr-ship). https://github.com/gastownhall/gascity-packs/tree/main/pr-pipeline git # pr pipeline author-side and review-side pr discipline distributed as a gas city pack. encodes the planning, blast-radius, scorecard-review, and pre-push gating workflows that careful contributors run by hand, so any city that imports this pack gets the same discipline as platform-native formulas and commands. ## status **v0.1.0** — initial release. ships six formulas: four with matching wrapper commands, plus two dispatched on demand. the four wrapper-command formulas: | formula | command | purpose | |---------|---------|---------| | `mol-pr-start` | `gc pr plan ` | issue → structured plan with blocking gates | | `mol-pr-blast-radius` | `gc pr blast-radius \"\"` | map impact surface (callers, configs, concurrency) | | `mol-pr-review` | `gc pr review ` | outgoing-pr self-review against an 11-category scorecard | | `mol-pr-ship` | `gc pr ship` | pre-push gate (simplify → review-iterate → contributor check); stops at report | all four wrapper-command formulas are read-only by default for filesystem changes outside their own output paths. `mol-pr-ship` may modify the diff during simplify and review-iteration stages; everything else writes only to `.gc/pr-pipeline//`. none of them push or open prs — those decisions stay with the caller. two further formulas ship without a wrapper command — they are dispatched on demand (typically by a maintenance pl) rather than run by hand: | formula | dispatch | purpose | |---------|----------|---------| | `mol-pr-triage` | `gc sling / mol-pr-triage --formula` | scan/classify open issues into a ranked work-queue | | `mol-pr-from-issue` | `gc sling / mol-pr-from-issue --formula --var issue_number=` | author-side macro chain: issue → plan → implement → ship gate → (optional) open pr | `mol-pr-from-issue` is slung as a routed molecule, so its github-issue input is named **`issue_number`** rather than `issue` — the bare `issue` var name is a reserved formulas-v2 alias for the routed work bead and would be clobbered. it defaults to halt-at-branch-ready (`auto_push` absent → no push, no pr); pass `--var auto_push=true` to authorize the eligibility-gated push. ## scope this pack is the **author side** — planning, building, and shipping the prs your city sends out. reviewing and merging **incoming** prs that arrive at your repo is a separate, maintainer-side concern and out of scope here. the one maintainer-side artifact shipped here is the adoption-review comment template (`templates/adoption-review-comment.md`); it reuses the same 11-category scorecard `mol-pr-review` applies, so review feedback keeps a consistent shape in both directions. ## testing across the layer boundary **[`docs/testing-across-the-layer-boundary.md`](docs/testing-across-the-layer-boundary.md)** is the standard behind the scorecard's evidence category, and the one to read before writing or reviewing a fix anywhere in the dolt / beads / gascity / packs stack. it answers two questions the scorecard assumes you have already settled. first, when does a test actually demonstrate that a fix repairs a live build, rather than repairing a graph of stubs? a tier ladder, a fidelity claim owed per replaced seam, and a second mutation that perturbs the stubs instead of only reverting the fix. second, which of the four repositories owns a given defect, with a routing checklist and three open issues on this tracker that name artifacts living in `gastownhall/gascity` rather than here. ## usage in your city's `pack.toml`: ```toml [imports.pr-pipeline] source = \"../packs/pr-pipeline\" # path; or git url when published ``` ### plan a pr for an issue ```sh gc pr-pipeline pr plan 1234 --rig api-server ``` the formula reads the issue, runs blocking gates (competing-pr and architectural-refactor checks), maps blast radius, checks repo conventions, writes a structured plan to `.gc/pr-pipeline/plans/issue-1234.md`, and audits the plan against 19 recurring review findings. **no code is written.** ### map blast radius for a freeform scope ```sh gc pr-pipeline pr blast-radius \"funcxyz in pkg/foo\" --rig api-server ``` for changes that don't start from an issue — refactors, hotfixes, exploratory deltas — `mol-pr-blast-radius` is a standalone entry point with the same analysis shape the planner runs inline. ### self-review an outgoing pr ```sh gc pr-pipeline pr review 1234 --rig api-server ``` scorecard against 11 categories (behavioral correctness, contract fidelity, blast radius, concurrency, error handling, security, resource lifecycle, release safety, test evidence, architectural consistency, debuggability). pre-flags 7 recurring fixup themes. verdict: `block`, `request_changes`, `approve`, or `too_large`. a pr whose additions+deletions exceed `--max-diff-lines` (default 5000, `0` disables) is refused before any agent is started, exiting 3 and recording `verdict: too_large`. that refusal describes the request, not a failed run: retrying it unchanged cannot succeed and spends the same budget again, so supervising loops must not treat it as retryable. ### run the pre-push gate ```sh gc pr-pipeline pr ship --rig api-server ``` four-stage pipeline: simplify → iterate an adversarial multi-reviewer panel (parallel reviewer subagents + adversarial synthesis) until clean → mechanical gates (build/vet/test/docs) → readiness report. **stops at the report.** push and pr-open are explicit caller actions this formula never performs. ### override the worker agent default agent for all wrappers is `polecat`. override with `--agent`: ```sh gc pr-pipeline pr plan 1234 --rig api-server --agent claude ``` or sling directly without the wrapper: ```sh gc sling api-server/polecat mol-pr-start --formula --var issue=1234 ``` ## pack contents ``` pr-pipeline/ ├── pack.toml ├── docs/ │ └── testing-across-the-layer-boundary.md tier ladder + layer map ├── formulas/ │ ├── mol-pr-start.formula.toml 6-step planner │ ├── mol-pr-blast-radius.formula.toml 5-step impact mapper │ ├── mol-pr-review.formula.toml 4-step outgoing-pr scorecard │ ├── mol-pr-ship.formula.toml 5-step pre-push gate │ ├── mol-pr-triage.formula.toml issue scan → ranked work-queue │ └── mol-pr-from-issue.formula.toml 7-step issue → branch-ready pr ├── commands/ │ └── pr/ │ ├── plan/ (run.sh + help.md) │ ├── blast-radius/ │ ├── review/ │ └── ship/ └── templates/ └── adoption-review-comment.md canonical reviewer-side comment template (form 1: no maintainer changes; form 2: maintainer fixups present) ``` ### shared templates `templates/adoption-review-comment.md` is the canonical structure for the synthesis comment a maintainer posts when adopting a contributor pr. two forms cover all four merge paths a maintainer-side adoption workflow distinguishes (no maintainer changes vs. maintainer fixups present). the template documents: - the two literal comment shapes (form 1 / form 2) - all inputs the renderer needs (pr metadata, synthesis + scorecard from the review run, maintainer-fixup log/diff, final tip sha, model list, iteration count) - rendering rules per `{model-rendered}` field (the orchestration is mechanical; the prose is the model's job) - validation gates enforced before posting (verbatim opener, footer literal, section order, sha prefix match, iteration-count match) - a fail-stop fallback for malformed renders (write rejected text and stop — never post a partial comment) - path-specific adjustments for the four merge paths (a / b / c / d) the reviewer-side counterpart to this pack's `mol-pr-review` author-side scorecard. both produce structured findings against the same 11-category scorecard so contributors get consistent feedback shape regardless of direction. the full workflow for each formula lives in step descriptions. a coding agent (polecat or equivalent) follows them in sequence; gates can short-circuit with an early exit. ## why formula-shaped, not agent-as-directory this pack ships **formulas**, not standing agents. each formula is a bounded workflow (\"plan one pr, exit\" / \"score one pr, exit\") rather than a long-lived role like mayor or polecat. the consumer city's existing coding worker (whatever it's named) runs the formula — no extra agent deployment required. standing roles (mayor, polecat, witness, refinery) belong in their own packs as `agents//` directories. bounded workflows belong as `formulas/mol-.formula.toml` with the workflow inlined in step descriptions. 0.1.0 initial pr-pipeline release: four author-side formulas (mol-pr-start, mol-pr-blast-radius, mol-pr-review, mol-pr-ship) + wrapper commands.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/pr-pipeline/README.md", - "content": "# PR Pipeline\n\nAuthor-side and review-side PR discipline distributed as a Gas City pack.\n\nEncodes the planning, blast-radius, scorecard-review, and pre-push gating\nworkflows that careful contributors run by hand, so any city that imports\nthis pack gets the same discipline as platform-native formulas and\ncommands.\n\n## Status\n\n**v0.1.0** — initial release. Ships six formulas: four with matching\nwrapper commands, plus two dispatched on demand. The four wrapper-command\nformulas:\n\n| Formula | Command | Purpose |\n|---------|---------|---------|\n| `mol-pr-start` | `gc pr plan ` | Issue → structured plan with BLOCKING gates |\n| `mol-pr-blast-radius` | `gc pr blast-radius \"\"` | Map impact surface (callers, configs, concurrency) |\n| `mol-pr-review` | `gc pr review ` | Outgoing-PR self-review against an 11-category scorecard |\n| `mol-pr-ship` | `gc pr ship` | Pre-push gate (simplify → review-iterate → contributor check); STOPS at report |\n\nAll four wrapper-command formulas are read-only by default for filesystem\nchanges outside their own output paths. `mol-pr-ship` may modify the diff\nduring simplify and review-iteration stages; everything else writes only\nto `.gc/pr-pipeline//`. None of them push or open PRs — those\ndecisions stay with the caller.\n\nTwo further formulas ship without a wrapper command — they are dispatched\non demand (typically by a maintenance PL) rather than run by hand:\n\n| Formula | Dispatch | Purpose |\n|---------|----------|---------|\n| `mol-pr-triage` | `gc sling / mol-pr-triage --formula` | Scan/classify open issues into a ranked work-queue |\n| `mol-pr-from-issue` | `gc sling / mol-pr-from-issue --formula --var issue_number=` | Author-side macro chain: issue → plan → implement → ship gate → (optional) open PR |\n\n`mol-pr-from-issue` is slung as a routed molecule, so its GitHub-issue\ninput is named **`issue_number`** rather than `issue` — the bare `issue`\nvar name is a reserved formulas-v2 alias for the routed work bead and\nwould be clobbered. It defaults to halt-at-branch-ready (`auto_push`\nabsent → no push, no PR); pass `--var auto_push=true` to authorize the\neligibility-gated push.\n\n## Scope\n\nThis pack is the **author side** — planning, building, and shipping the\nPRs your city sends out. Reviewing and merging **incoming** PRs that\narrive at your repo is a separate, maintainer-side concern and out of\nscope here.\n\nThe one maintainer-side artifact shipped here is the adoption-review\ncomment template (`templates/adoption-review-comment.md`); it reuses the\nsame 11-category scorecard `mol-pr-review` applies, so review feedback\nkeeps a consistent shape in both directions.\n\n## Usage\n\nIn your city's `pack.toml`:\n\n```toml\n[imports.pr-pipeline]\nsource = \"../packs/pr-pipeline\" # path; or git URL when published\n```\n\n### Plan a PR for an issue\n\n```sh\ngc pr-pipeline pr plan 1234 --rig api-server\n```\n\nThe formula reads the issue, runs BLOCKING gates (competing-PR and\narchitectural-refactor checks), maps blast radius, checks repo\nconventions, writes a structured plan to\n`.gc/pr-pipeline/plans/issue-1234.md`, and audits the plan against 19\nrecurring review findings. **No code is written.**\n\n### Map blast radius for a freeform scope\n\n```sh\ngc pr-pipeline pr blast-radius \"FuncXYZ in pkg/foo\" --rig api-server\n```\n\nFor changes that don't start from an issue — refactors, hotfixes,\nexploratory deltas — `mol-pr-blast-radius` is a standalone entry point\nwith the same analysis shape the planner runs inline.\n\n### Self-review an outgoing PR\n\n```sh\ngc pr-pipeline pr review 1234 --rig api-server\n```\n\nScorecard against 11 categories (behavioral correctness, contract\nfidelity, blast radius, concurrency, error handling, security, resource\nlifecycle, release safety, test evidence, architectural consistency,\ndebuggability). Pre-flags 7 recurring fixup themes. Verdict: `block`,\n`request_changes`, or `approve`.\n\n### Run the pre-push gate\n\n```sh\ngc pr-pipeline pr ship --rig api-server\n```\n\nFour-stage pipeline: simplify → iterate an adversarial multi-reviewer\npanel (parallel reviewer subagents + adversarial synthesis) until clean →\nmechanical gates (build/vet/test/docs) → readiness report. **STOPS at\nthe report.** Push and PR-open are explicit caller actions this formula\nnever performs.\n\n### Override the worker agent\n\nDefault agent for all wrappers is `polecat`. Override with `--agent`:\n\n```sh\ngc pr-pipeline pr plan 1234 --rig api-server --agent claude\n```\n\nOr sling directly without the wrapper:\n\n```sh\ngc sling api-server/polecat mol-pr-start --formula --var issue=1234\n```\n\n## Pack contents\n\n```\npr-pipeline/\n├── pack.toml\n├── formulas/\n│ ├── mol-pr-start.formula.toml 6-step planner\n│ ├── mol-pr-blast-radius.formula.toml 5-step impact mapper\n│ ├── mol-pr-review.formula.toml 4-step outgoing-PR scorecard\n│ ├── mol-pr-ship.formula.toml 5-step pre-push gate\n│ ├── mol-pr-triage.formula.toml issue scan → ranked work-queue\n│ └── mol-pr-from-issue.formula.toml 7-step issue → branch-ready PR\n├── commands/\n│ └── pr/\n│ ├── plan/ (run.sh + help.md)\n│ ├── blast-radius/\n│ ├── review/\n│ └── ship/\n└── templates/\n └── adoption-review-comment.md canonical reviewer-side\n comment template (Form 1: no\n maintainer changes; Form 2:\n maintainer fixups present)\n```\n\n### Shared templates\n\n`templates/adoption-review-comment.md` is the canonical structure for the\nsynthesis comment a maintainer posts when adopting a contributor PR. Two\nforms cover all four merge paths a maintainer-side adoption workflow\ndistinguishes (no maintainer changes vs. maintainer fixups present).\n\nThe template documents:\n\n- The two literal comment shapes (Form 1 / Form 2)\n- All inputs the renderer needs (PR metadata, synthesis + scorecard from\n the review run, maintainer-fixup log/diff, final tip SHA, model list,\n iteration count)\n- Rendering rules per `{model-rendered}` field (the orchestration is\n mechanical; the prose is the model's job)\n- Validation gates enforced before posting (verbatim opener, footer\n literal, section order, SHA prefix match, iteration-count match)\n- A fail-stop fallback for malformed renders (write rejected text and\n STOP — never post a partial comment)\n- Path-specific adjustments for the four merge paths (A / B / C / D)\n\nThe reviewer-side counterpart to this pack's `mol-pr-review`\nauthor-side scorecard. Both produce structured findings against the same\n11-category scorecard so contributors get consistent feedback shape\nregardless of direction.\n\nThe full workflow for each formula lives in step descriptions. A coding\nagent (polecat or equivalent) follows them in sequence; gates can\nshort-circuit with an early exit.\n\n## Why formula-shaped, not agent-as-directory\n\nThis pack ships **formulas**, not standing agents. Each formula is a\nbounded workflow (\"plan one PR, exit\" / \"score one PR, exit\") rather\nthan a long-lived role like mayor or polecat. The consumer city's\nexisting coding worker (whatever it's named) runs the formula — no\nextra agent deployment required.\n\nStanding roles (mayor, polecat, witness, refinery) belong in their own\npacks as `agents//` directories. Bounded workflows belong as\n`formulas/mol-.formula.toml` with the workflow inlined in step\ndescriptions." + "content": "# PR Pipeline\n\nAuthor-side and review-side PR discipline distributed as a Gas City pack.\n\nEncodes the planning, blast-radius, scorecard-review, and pre-push gating\nworkflows that careful contributors run by hand, so any city that imports\nthis pack gets the same discipline as platform-native formulas and\ncommands.\n\n## Status\n\n**v0.1.0** — initial release. Ships six formulas: four with matching\nwrapper commands, plus two dispatched on demand. The four wrapper-command\nformulas:\n\n| Formula | Command | Purpose |\n|---------|---------|---------|\n| `mol-pr-start` | `gc pr plan ` | Issue → structured plan with BLOCKING gates |\n| `mol-pr-blast-radius` | `gc pr blast-radius \"\"` | Map impact surface (callers, configs, concurrency) |\n| `mol-pr-review` | `gc pr review ` | Outgoing-PR self-review against an 11-category scorecard |\n| `mol-pr-ship` | `gc pr ship` | Pre-push gate (simplify → review-iterate → contributor check); STOPS at report |\n\nAll four wrapper-command formulas are read-only by default for filesystem\nchanges outside their own output paths. `mol-pr-ship` may modify the diff\nduring simplify and review-iteration stages; everything else writes only\nto `.gc/pr-pipeline//`. None of them push or open PRs — those\ndecisions stay with the caller.\n\nTwo further formulas ship without a wrapper command — they are dispatched\non demand (typically by a maintenance PL) rather than run by hand:\n\n| Formula | Dispatch | Purpose |\n|---------|----------|---------|\n| `mol-pr-triage` | `gc sling / mol-pr-triage --formula` | Scan/classify open issues into a ranked work-queue |\n| `mol-pr-from-issue` | `gc sling / mol-pr-from-issue --formula --var issue_number=` | Author-side macro chain: issue → plan → implement → ship gate → (optional) open PR |\n\n`mol-pr-from-issue` is slung as a routed molecule, so its GitHub-issue\ninput is named **`issue_number`** rather than `issue` — the bare `issue`\nvar name is a reserved formulas-v2 alias for the routed work bead and\nwould be clobbered. It defaults to halt-at-branch-ready (`auto_push`\nabsent → no push, no PR); pass `--var auto_push=true` to authorize the\neligibility-gated push.\n\n## Scope\n\nThis pack is the **author side** — planning, building, and shipping the\nPRs your city sends out. Reviewing and merging **incoming** PRs that\narrive at your repo is a separate, maintainer-side concern and out of\nscope here.\n\nThe one maintainer-side artifact shipped here is the adoption-review\ncomment template (`templates/adoption-review-comment.md`); it reuses the\nsame 11-category scorecard `mol-pr-review` applies, so review feedback\nkeeps a consistent shape in both directions.\n\n## Testing across the layer boundary\n\n**[`docs/testing-across-the-layer-boundary.md`](docs/testing-across-the-layer-boundary.md)**\nis the standard behind the scorecard's evidence category, and the one to read\nbefore writing or reviewing a fix anywhere in the dolt / beads / gascity / packs\nstack.\n\nIt answers two questions the scorecard assumes you have already settled. First,\nwhen does a test actually demonstrate that a fix repairs a live build, rather\nthan repairing a graph of stubs? A tier ladder, a fidelity claim owed per\nreplaced seam, and a second mutation that perturbs the stubs instead of only\nreverting the fix. Second, which of the four repositories owns a given defect,\nwith a routing checklist and three open issues on this tracker that name\nartifacts living in `gastownhall/gascity` rather than here.\n\n## Usage\n\nIn your city's `pack.toml`:\n\n```toml\n[imports.pr-pipeline]\nsource = \"../packs/pr-pipeline\" # path; or git URL when published\n```\n\n### Plan a PR for an issue\n\n```sh\ngc pr-pipeline pr plan 1234 --rig api-server\n```\n\nThe formula reads the issue, runs BLOCKING gates (competing-PR and\narchitectural-refactor checks), maps blast radius, checks repo\nconventions, writes a structured plan to\n`.gc/pr-pipeline/plans/issue-1234.md`, and audits the plan against 19\nrecurring review findings. **No code is written.**\n\n### Map blast radius for a freeform scope\n\n```sh\ngc pr-pipeline pr blast-radius \"FuncXYZ in pkg/foo\" --rig api-server\n```\n\nFor changes that don't start from an issue — refactors, hotfixes,\nexploratory deltas — `mol-pr-blast-radius` is a standalone entry point\nwith the same analysis shape the planner runs inline.\n\n### Self-review an outgoing PR\n\n```sh\ngc pr-pipeline pr review 1234 --rig api-server\n```\n\nScorecard against 11 categories (behavioral correctness, contract\nfidelity, blast radius, concurrency, error handling, security, resource\nlifecycle, release safety, test evidence, architectural consistency,\ndebuggability). Pre-flags 7 recurring fixup themes. Verdict: `block`,\n`request_changes`, `approve`, or `too_large`.\n\nA PR whose additions+deletions exceed `--max-diff-lines` (default 5000, `0`\ndisables) is refused before any agent is started, exiting 3 and recording\n`verdict: too_large`. That refusal describes the request, not a failed run:\nretrying it unchanged cannot succeed and spends the same budget again, so\nsupervising loops must not treat it as retryable.\n\n### Run the pre-push gate\n\n```sh\ngc pr-pipeline pr ship --rig api-server\n```\n\nFour-stage pipeline: simplify → iterate an adversarial multi-reviewer\npanel (parallel reviewer subagents + adversarial synthesis) until clean →\nmechanical gates (build/vet/test/docs) → readiness report. **STOPS at\nthe report.** Push and PR-open are explicit caller actions this formula\nnever performs.\n\n### Override the worker agent\n\nDefault agent for all wrappers is `polecat`. Override with `--agent`:\n\n```sh\ngc pr-pipeline pr plan 1234 --rig api-server --agent claude\n```\n\nOr sling directly without the wrapper:\n\n```sh\ngc sling api-server/polecat mol-pr-start --formula --var issue=1234\n```\n\n## Pack contents\n\n```\npr-pipeline/\n├── pack.toml\n├── docs/\n│ └── testing-across-the-layer-boundary.md tier ladder + layer map\n├── formulas/\n│ ├── mol-pr-start.formula.toml 6-step planner\n│ ├── mol-pr-blast-radius.formula.toml 5-step impact mapper\n│ ├── mol-pr-review.formula.toml 4-step outgoing-PR scorecard\n│ ├── mol-pr-ship.formula.toml 5-step pre-push gate\n│ ├── mol-pr-triage.formula.toml issue scan → ranked work-queue\n│ └── mol-pr-from-issue.formula.toml 7-step issue → branch-ready PR\n├── commands/\n│ └── pr/\n│ ├── plan/ (run.sh + help.md)\n│ ├── blast-radius/\n│ ├── review/\n│ └── ship/\n└── templates/\n └── adoption-review-comment.md canonical reviewer-side\n comment template (Form 1: no\n maintainer changes; Form 2:\n maintainer fixups present)\n```\n\n### Shared templates\n\n`templates/adoption-review-comment.md` is the canonical structure for the\nsynthesis comment a maintainer posts when adopting a contributor PR. Two\nforms cover all four merge paths a maintainer-side adoption workflow\ndistinguishes (no maintainer changes vs. maintainer fixups present).\n\nThe template documents:\n\n- The two literal comment shapes (Form 1 / Form 2)\n- All inputs the renderer needs (PR metadata, synthesis + scorecard from\n the review run, maintainer-fixup log/diff, final tip SHA, model list,\n iteration count)\n- Rendering rules per `{model-rendered}` field (the orchestration is\n mechanical; the prose is the model's job)\n- Validation gates enforced before posting (verbatim opener, footer\n literal, section order, SHA prefix match, iteration-count match)\n- A fail-stop fallback for malformed renders (write rejected text and\n STOP — never post a partial comment)\n- Path-specific adjustments for the four merge paths (A / B / C / D)\n\nThe reviewer-side counterpart to this pack's `mol-pr-review`\nauthor-side scorecard. Both produce structured findings against the same\n11-category scorecard so contributors get consistent feedback shape\nregardless of direction.\n\nThe full workflow for each formula lives in step descriptions. A coding\nagent (polecat or equivalent) follows them in sequence; gates can\nshort-circuit with an early exit.\n\n## Why formula-shaped, not agent-as-directory\n\nThis pack ships **formulas**, not standing agents. Each formula is a\nbounded workflow (\"plan one PR, exit\" / \"score one PR, exit\") rather\nthan a long-lived role like mayor or polecat. The consumer city's\nexisting coding worker (whatever it's named) runs the formula — no\nextra agent deployment required.\n\nStanding roles (mayor, polecat, witness, refinery) belong in their own\npacks as `agents//` directories. Bounded workflows belong as\n`formulas/mol-.formula.toml` with the workflow inlined in step\ndescriptions." }, "og_image": "/og/pr-pipeline.svg", "latest": "0.1.0", @@ -470,13 +478,13 @@ "description": "Shared Slack channel routing and session identity pack for Gas City.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/slack-channel", "source_kind": "git", - "search_text": "slack-channel gascity-packs maintained gas city shared slack channel routing and session identity pack for gas city. https://github.com/gastownhall/gascity-packs/tree/main/slack-channel git # slack-channel **bridge slack channels and dms to your gas city sessions.** bind a channel to one or more gc sessions and every message there is delivered to them; reply, publish, and react back; give each session its own slack identity; address a session from any channel with `@handle`. slack-channel is **tier 2** of the slack pack family — the \"team channel ↔ session graph\" middle tier. it is built on slack-mini's single-file kernel and adds the state tier 1 deliberately omits. it does **not** include the multi-rig routing, slash-command intake, peer fanout, or file upload of `slack-full` (tier 3). > pick exactly one slack tier per city. the tiers are alternatives, not > layers you stack. ## what you get - **bound channels:** `bind-room c0123 sess-a sess-b` — every human message in `c0123` is delivered to both sessions (not just `@`-mentions). - **address by handle:** `@mayor: status?` in any channel routes to the session aliased to `mayor`. - **reply / publish:** `reply-current` threads under the message that just arrived; `publish` posts into a session's bound channel; `publish-to-channel` posts anywhere by id. - **per-session identity:** each session posts under its own username + avatar (`chat:write.customize`). - **react:** `react --emoji eyes` drops a receipt on the latest inbound. ## verbs all verbs are `gc slack-channel `. run any with `--help` for full flags. | verb | purpose | | --- | --- | | `bind-dm ` | bind a dm to one or more sessions. | | `bind-room ` | bind a channel to one or more sessions. | | `publish --body ` | post into the current session's bound channel. | | `publish-to-channel --channel --body ` | post into any channel by id. | | `reply-current --body [--thread-current]` | reply into the session's latest inbound conversation. | | `react [--emoji ]` | react on the session's latest inbound message. | | `identity --as [--avatar-emoji ]` | set the session's slack username + avatar. | | `handle-alias --handle --session ` | map `@` to a session. | ## install ### 1. create the slack app create an app from the shipped manifest at [`manifest/app.json`](./manifest/app.json): 1. → **create new app** → **from a manifest**. 2. pick your workspace, paste `manifest/app.json`, create. 3. **install to workspace** and copy the **bot user oauth token** (`xoxb-…`). 4. from **basic information**, copy the **signing secret**. the manifest requests the scopes tier 2 needs: `app_mentions:read`, `channels:history`, `groups:history`, `im:history`, `mpim:history`, `chat:write`, `chat:write.public`, `chat:write.customize`, `reactions:write`; and subscribes to `app_mention` plus `message.*`. > `chat:write.customize` is what makes per-session `identity` overrides > visible. without it, slack ignores the username/avatar and posts fall > through under the default bot identity. ### 2. configure the city import the pack in your city's `pack.toml`: ```toml [imports.slack-channel] source = \"../packs/slack-channel\" ``` provide the adapter's environment: ```sh slack_bot_token=xoxb-... # from step 1 slack_signing_secret=... # from step 1 slack_workspace_id=t0123abcd # your slack team id gc_city_name= # the gc city to bridge into gc_city_path=/path/to/your/city # on-disk city root (for the registries) ``` `gc_city_path` is where the three registries live (`/.gc/slack-channel/`). override the directory with `slack_channel_registry_dir` if needed. ### 3. expose the events endpoint and start the adapter listens for slack events on public tcp (`listen_public`, default `0.0.0.0:8775`). terminate tls in front of it and give slack a public url — [tailscale funnel](https://tailscale.com/kb/1223/funnel) is the easy path: ```sh tailscale funnel 8775 ``` then in **event subscriptions**, set the request url to `https:///slack/events`. slack sends a one-time `url_verification` challenge, which the adapter answers automatically. gc supervises the adapter as a `proxy_process` service (named `slack-channel`); building the binary is a one-time `go build` in `adapter/` (see [build](#build)). start your city. ## usage walkthrough ```sh # bind a channel to the pl and reviewer sessions. gc slack-channel bind-room c0123 sess-pl sess-reviewer # give the pl session its own identity (call once at session start). gc slack-channel identity --session sess-pl --as \"gas city pl\" --avatar-emoji robot_face # let humans address the mayor from any channel. gc slack-channel handle-alias --handle mayor --session sess-mayor # a human posts in c0123 → both bound sessions receive it. the pl replies # in-thread: gc slack-channel reply-current --body \"on it — see pr #42\" --thread-current # drop a receipt on the message that just arrived. gc slack-channel react --emoji eyes # unprompted status post into the bound channel. gc slack-channel publish --body \"nightly build is green\" ``` ## registries the adapter owns three on-disk registries under `/.gc/slack-channel/`. json schemas live in [`schema/`](./schema): | file | schema | contents | | --- | --- | --- | | `channel_mappings.json` | [channel_mappings.schema.json](./schema/channel_mappings.schema.json) | channel → bound sessions | | `identities.json` | [identities.schema.json](./schema/identities.schema.json) | session → username/avatar | | `handle_aliases.json` | [handle_aliases.schema.json](./schema/handle_aliases.schema.json) | handle → session | the adapter is the only writer and reader; the verbs mutate them by posting to the adapter, never by touching the files directly. ## build the adapter is a small multi-file go module with no external dependencies: ```sh cd adapter go build -o gc-slack-channel-adapter ./... go test ./... ``` the built binary is git-ignored; the `[[service]]` block runs it in place. ## configuration reference | variable | required | default | purpose | | --- | :---: | --- | --- | | `slack_bot_token` | ✓ | — | bot token for `chat.postmessage` + `reactions.add`. | | `slack_signing_secret` | ✓ | — | hmac secret for verifying slack requests. | | `slack_workspace_id` | ✓ | — | slack team id (extmsg account id + registry key). | | `gc_city_name` | ✓ | — | gc city to bridge into. | | `gc_city_path` | ✓* | — | city root; registry dir defaults under it. | | `slack_channel_registry_dir` | | `/.gc/slack-channel` | override the registry directory. | | `listen_public` | | `0.0.0.0:8775` | public bind for `/slack/events` + `/slack/interactions`. | | `listen_internal` | | `127.0.0.1:8776` | tcp bind for the verb endpoints when not a gc proxy_process. | | `register_on_start` | | `true` | self-register as an extmsg adapter on start. | | `slack_channel_inbound_target` | | `mayor` | fallback session for an unbound, unaliased `app_mention`. | | `slack_api_base` | | `https://slack.com/api` | slack web api origin (override for relays/tests). | | `gc_api_base_url` | | `http://127.0.0.1:9443` | gc api base. | \\* either `gc_city_path` or `slack_channel_registry_dir` must be set so the registries have a home. `gc_service_socket`, `gc_service_url_prefix`, and `gc_api_base_url` are injected by gc when the adapter runs as a `proxy_process` service. ### optional: interactive payloads to enable the basic modal-button ack, turn on **interactivity** in the slack app and point its request url at `https:///slack/interactions`. the adapter verifies the signature and acks with `200` so slack dismisses the interaction; it does no custom modal handling (that is tier 3). ## security posture a few deliberate choices in the shipped manifest and adapter are worth understanding before you install: - **`chat:write.public` (manifest scope).** this lets the bot post to any *public* channel without first being invited — `publish-to-channel --channel ` reaches any public channel in the workspace, and a bound session can publish to a public channel it was bound to without a join step. the blast radius is \"every public channel in this workspace.\" if that is broader than you want, drop `chat:write.public` from [`manifest/app.json`](./manifest/app.json) and invite the bot to each channel it should post in (slack then requires `conversations.join` / manual membership instead). - **`token_rotation_enabled: false` (manifest setting).** the adapter authenticates with a long-lived bot token (`slack_bot_token`) rather than rotating, expiring tokens. rotation would require the adapter to persist and refresh credentials; for a single-workspace tier-2 bridge the static token is simpler and the token never leaves the adapter (verbs never see it). treat the token as a secret, scope it to one workspace, and rotate it by hand if it is exposed. enable rotation in the slack app settings if your deployment policy requires it. - **internal verb listener is loopback / dev-only.** when the adapter is *not* run as a gc `proxy_process` service (`gc_service_socket` unset), the verb endpoints are served over plain tcp on `127.0.0.1:8776` (`listen_internal`) with no authentication — anything that can reach that loopback port can mutate the registries. this mode is for local development only. in production gc runs the adapter as a `proxy_process` service and the verb endpoints bind a `0600` unix-domain socket (`gc_service_socket`) that only the gc supervisor can reach; do not expose `127.0.0.1:8776` beyond loopback. ## choosing a tier - **slack-mini** (tier 1) — one outbound verb, `app_mention` → mayor. no registries. - **slack-channel** (tier 2, this pack) — channel bindings, per-session identity, handle aliases. single workspace, single rig. - **slack-full** (tier 3) — multi-rig routing, slash commands, peer fanout, file upload, modal flows. the bot token, signing secret, and workspace id carry over unchanged when you move between tiers. 0.1.0 initial slack channel pack release with canonical content hash. 0.1.1 ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark schema test executable to align release hash with materialized modes.", + "search_text": "slack-channel gascity-packs maintained gas city shared slack channel routing and session identity pack for gas city. https://github.com/gastownhall/gascity-packs/tree/main/slack-channel git # slack-channel **bridge slack channels and dms to your gas city sessions.** bind a channel to one or more gc sessions and every message there is delivered to them; reply, publish, and react back; give each session its own slack identity; address a session from any channel with `@handle`. slack-channel is **tier 2** of the slack pack family — the \"team channel ↔ session graph\" middle tier. it is built on slack-mini's single-file kernel and adds the state tier 1 deliberately omits. it does **not** include the multi-rig routing, slash-command intake, peer fanout, or file upload of `slack-full` (tier 3). > pick exactly one slack tier per city. the tiers are alternatives, not > layers you stack. ## what you get - **bound channels:** `bind-room c0123 sess-a sess-b` — every human message in `c0123` is delivered to both sessions (not just `@`-mentions). - **address by handle:** `@mayor: status?` in any channel routes to the session aliased to `mayor`. - **reply / publish:** `reply-current` threads under the message that just arrived; `publish` posts into a session's bound channel; `publish-to-channel` posts anywhere by id. - **per-session identity:** each session posts under its own username + avatar (`chat:write.customize`). - **react:** `react --emoji eyes` drops a receipt on the latest inbound. ## verbs all verbs are `gc slack-channel `. run any with `--help` for full flags. | verb | purpose | | --- | --- | | `bind-dm ` | bind a dm to one or more sessions. | | `bind-room ` | bind a channel to one or more sessions. | | `publish --body ` | post into the current session's bound channel. | | `publish-to-channel --channel --body ` | post into any channel by id. | | `reply-current --body [--thread-current]` | reply into the session's latest inbound conversation. | | `react [--emoji ]` | react on the session's latest inbound message. | | `identity --as [--avatar-emoji ]` | set the session's slack username + avatar. | | `handle-alias --handle --session ` | map `@` to a session. | ## install ### choose a transport inbound messages reach the adapter one of two ways. pick one before you create the app — the choice is baked into the app manifest. | | **socket mode** | **http (events api)** | | --- | --- | --- | | public ingress | none | public https url required | | how events arrive | outbound websocket the adapter opens to slack | slack posts to your request url | | credentials | app-level token (`xapp-…`) | signing secret | | manifest | [`manifest/app-socket.json`](./manifest/app-socket.json) | [`manifest/app.json`](./manifest/app.json) | | interactivity (opt-in) | not supported | `/slack/interactions` | **socket mode is the right default if you cannot expose an inbound port** — behind a corporate firewall, on a laptop, or anywhere a tunnel is blocked. it needs only outbound https to `slack.com`. bindings, handle aliases, identities, and every outbound verb behave identically on both. the one thing socket mode cannot carry is interactivity. tier 2 ships it disabled, so this only matters if you deliberately turned on `/slack/interactions` — that half still needs a public url. ### 1. create the slack app create an app from the shipped manifest — `manifest/app-socket.json` for socket mode, `manifest/app.json` for http: 1. → **create new app** → **from a manifest**. 2. pick your workspace, paste the manifest, create. 3. **install to workspace** and copy the **bot user oauth token** (`xoxb-…`). 4. from **basic information**: - **socket mode:** under **app-level tokens**, **generate token and scopes**, add the **`connections:write`** scope, and copy the token (`xapp-…`). app-level tokens cannot be declared in a manifest, so this step is manual. - **http:** copy the **signing secret**. the manifest requests the scopes tier 2 needs: `app_mentions:read`, `channels:history`, `groups:history`, `im:history`, `mpim:history`, `chat:write`, `chat:write.public`, `chat:write.customize`, `reactions:write`; and subscribes to `app_mention` plus `message.*`. > `chat:write.customize` is what makes per-session `identity` overrides > visible. without it, slack ignores the username/avatar and posts fall > through under the default bot identity. ### 2. configure the city import the pack into your city: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//slack-channel ``` that writes the import and pins the release in `packs.lock`. the same import by hand in `pack.toml`, followed by `gc import install`: ```toml [imports.slack-channel] source = \"https://github.com/gastownhall/gascity-packs.git//slack-channel\" ``` `gc pack registry show slack-channel` prints the registry entry and its releases. to work on the pack from a local checkout, point `source` at the path instead: ```toml [imports.slack-channel] source = \"../gascity-packs/slack-channel\" ``` provide the adapter's environment: ```sh slack_bot_token=xoxb-... # from step 1 slack_app_token=xapp-... # socket mode only — selects the transport slack_signing_secret=... # http only slack_workspace_id=t0123abcd # your slack team id gc_city_name= # the gc city to bridge into gc_city_path=/path/to/your/city # on-disk city root (for the registries) ``` setting `slack_app_token` is the whole switch: the adapter runs socket mode, never binds `listen_public`, and stops requiring `slack_signing_secret` (socket mode has no request signatures to verify). `gc_city_path` is where the three registries live (`/.gc/slack-channel/`). override the directory with `slack_channel_registry_dir` if needed. ### 3. start **socket mode — nothing to expose.** the adapter dials out to slack, so there is no listener to publish, no tls to terminate, and no request url to register. start your city; the log line to look for is `socket mode: connected`. **http — expose the events endpoint.** the adapter listens for slack events on public tcp (`listen_public`, default `0.0.0.0:8775`). terminate tls in front of it and give slack a public url — [tailscale funnel](https://tailscale.com/kb/1223/funnel) is the easy path: ```sh tailscale funnel 8775 ``` then in **event subscriptions**, set the request url to `https:///slack/events`. slack sends a one-time `url_verification` challenge, which the adapter answers automatically. gc supervises the adapter as a `proxy_process` service (named `slack-channel`); the service command is `adapter/run.sh`, which builds the binary from source on first start (see [build](#build)). start your city. ## usage walkthrough ```sh # bind a channel to the pl and reviewer sessions. gc slack-channel bind-room c0123 sess-pl sess-reviewer # give the pl session its own identity (call once at session start). gc slack-channel identity --session sess-pl --as \"gas city pl\" --avatar-emoji robot_face # let humans address the mayor from any channel. gc slack-channel handle-alias --handle mayor --session sess-mayor # a human posts in c0123 → both bound sessions receive it. the pl replies # in-thread: gc slack-channel reply-current --body \"on it — see pr #42\" --thread-current # drop a receipt on the message that just arrived. gc slack-channel react --emoji eyes # unprompted status post into the bound channel. gc slack-channel publish --body \"nightly build is green\" ``` ## registries the adapter owns three on-disk registries under `/.gc/slack-channel/`. json schemas live in [`schema/`](./schema): | file | schema | contents | | --- | --- | --- | | `channel_mappings.json` | [channel_mappings.schema.json](./schema/channel_mappings.schema.json) | channel → bound sessions | | `identities.json` | [identities.schema.json](./schema/identities.schema.json) | session → username/avatar | | `handle_aliases.json` | [handle_aliases.schema.json](./schema/handle_aliases.schema.json) | handle → session | the adapter is the only writer and reader; the verbs mutate them by posting to the adapter, never by touching the files directly. ## build the adapter is a small multi-file go module with no external dependencies: ```sh cd adapter go build -o gc-slack-channel-adapter ./... go test ./... ``` the built binary is git-ignored. the `[[service]]` block runs `adapter/run.sh`, which execs the binary when it exists and otherwise builds it from the sources next to it first, so a pack pin bump (`gc import install` re-materializes the pack from git only) no longer strands the service. the first build runs inside gc's readiness window, so expect one or more restarts until the go build cache is warm; a go toolchain must be installed. the built binary is git-ignored; the `[[service]]` block runs it in place. ## configuration reference | variable | required | default | purpose | | --- | :---: | --- | --- | | `slack_bot_token` | ✓ | — | bot token for `chat.postmessage` + `reactions.add`. | | `slack_signing_secret` | http only | — | hmac secret for verifying slack requests. required unless `slack_app_token` is set. | | `slack_app_token` | socket mode | — | app-level token (`xapp-…`) with `connections:write`. setting it selects socket mode: no public listener, no signing secret. | | `slack_workspace_id` | ✓ | — | slack team id (extmsg account id + registry key). | | `gc_city_name` | ✓ | — | gc city to bridge into. | | `gc_city_path` | ✓* | — | city root; registry dir defaults under it. | | `slack_channel_registry_dir` | | `/.gc/slack-channel` | override the registry directory. | | `listen_public` | | `0.0.0.0:8775` | public bind for `/slack/events` + `/slack/interactions`. not bound in socket mode. | | `listen_internal` | | `127.0.0.1:8776` | tcp bind for the verb endpoints when not a gc proxy_process. | | `register_on_start` | | `true` | self-register as an extmsg adapter on start. | | `slack_channel_inbound_target` | | `mayor` | fallback session for an unbound, unaliased `app_mention`. | | `slack_api_base` | | `https://slack.com/api` | slack web api origin (override for relays/tests). | | `gc_api_base_url` | | `http://127.0.0.1:9443` | gc api base. | \\* either `gc_city_path` or `slack_channel_registry_dir` must be set so the registries have a home. `gc_service_socket`, `gc_service_url_prefix`, and `gc_api_base_url` are injected by gc when the adapter runs as a `proxy_process` service. ### optional: interactive payloads to enable the basic modal-button ack, turn on **interactivity** in the slack app and point its request url at `https:///slack/interactions`. the adapter verifies the signature and acks with `200` so slack dismisses the interaction; it does no custom modal handling (that is tier 3). ## security posture a few deliberate choices in the shipped manifest and adapter are worth understanding before you install: - **`chat:write.public` (manifest scope).** this lets the bot post to any *public* channel without first being invited — `publish-to-channel --channel ` reaches any public channel in the workspace, and a bound session can publish to a public channel it was bound to without a join step. the blast radius is \"every public channel in this workspace.\" if that is broader than you want, drop `chat:write.public` from [`manifest/app.json`](./manifest/app.json) and invite the bot to each channel it should post in (slack then requires `conversations.join` / manual membership instead). - **`token_rotation_enabled: false` (manifest setting).** the adapter authenticates with a long-lived bot token (`slack_bot_token`) rather than rotating, expiring tokens. rotation would require the adapter to persist and refresh credentials; for a single-workspace tier-2 bridge the static token is simpler and the token never leaves the adapter (verbs never see it). treat the token as a secret, scope it to one workspace, and rotate it by hand if it is exposed. enable rotation in the slack app settings if your deployment policy requires it. - **events from another workspace are dropped, on both transports.** neither transport establishes which workspace an event came from: socket mode has no signature at all, and an http request signature proves only that slack sent the event *for this app*, not that it came *from this team*. an app installed in a second workspace would otherwise have that workspace's messages filed under `slack_workspace_id` and matched against this workspace's channel bindings, so an event whose `team_id` is set and does not match is dropped with a log line. run one workspace per install. - **internal verb listener is loopback / dev-only.** when the adapter is *not* run as a gc `proxy_process` service (`gc_service_socket` unset), the verb endpoints are served over plain tcp on `127.0.0.1:8776` (`listen_internal`) with no authentication — anything that can reach that loopback port can mutate the registries. this mode is for local development only. in production gc runs the adapter as a `proxy_process` service and the verb endpoints bind a `0600` unix-domain socket (`gc_service_socket`) that only the gc supervisor can reach; do not expose `127.0.0.1:8776` beyond loopback. ## choosing a tier - **slack-mini** (tier 1) — one outbound verb, `app_mention` → mayor. no registries. - **slack-channel** (tier 2, this pack) — channel bindings, per-session identity, handle aliases. single workspace, single rig. - **slack-full** (tier 3) — multi-rig routing, slash commands, peer fanout, file upload, modal flows. the bot token, signing secret, and workspace id carry over unchanged when you move between tiers. 0.1.0 initial slack channel pack release with canonical content hash. 0.1.1 ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark schema test executable to align release hash with materialized modes. 0.1.2 socket mode transport, adapter registration retry during city startup, a checked-in run.sh that rebuilds a missing adapter binary, and install docs for the git import.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/slack-channel/README.md", - "content": "# slack-channel\n\n**Bridge Slack channels and DMs to your Gas City sessions.** Bind a channel\nto one or more gc sessions and every message there is delivered to them;\nreply, publish, and react back; give each session its own Slack identity;\naddress a session from any channel with `@handle`.\n\nslack-channel is **Tier 2** of the Slack pack family — the \"team channel ↔\nsession graph\" middle tier. It is built on slack-mini's single-file kernel\nand adds the state Tier 1 deliberately omits. It does **not** include the\nmulti-rig routing, slash-command intake, peer fanout, or file upload of\n`slack-full` (Tier 3).\n\n> Pick exactly one Slack tier per city. The tiers are alternatives, not\n> layers you stack.\n\n## What you get\n\n- **Bound channels:** `bind-room C0123 sess-a sess-b` — every human message\n in `C0123` is delivered to both sessions (not just `@`-mentions).\n- **Address by handle:** `@mayor: status?` in any channel routes to the\n session aliased to `mayor`.\n- **Reply / publish:** `reply-current` threads under the message that just\n arrived; `publish` posts into a session's bound channel;\n `publish-to-channel` posts anywhere by id.\n- **Per-session identity:** each session posts under its own username +\n avatar (`chat:write.customize`).\n- **React:** `react --emoji eyes` drops a receipt on the latest inbound.\n\n## Verbs\n\nAll verbs are `gc slack-channel `. Run any with `--help` for full\nflags.\n\n| Verb | Purpose |\n| --- | --- |\n| `bind-dm ` | Bind a DM to one or more sessions. |\n| `bind-room ` | Bind a channel to one or more sessions. |\n| `publish --body ` | Post into the current session's bound channel. |\n| `publish-to-channel --channel --body ` | Post into any channel by id. |\n| `reply-current --body [--thread-current]` | Reply into the session's latest inbound conversation. |\n| `react [--emoji ]` | React on the session's latest inbound message. |\n| `identity --as [--avatar-emoji ]` | Set the session's Slack username + avatar. |\n| `handle-alias --handle --session ` | Map `@` to a session. |\n\n## Install\n\n### 1. Create the Slack app\n\nCreate an app from the shipped manifest at\n[`manifest/app.json`](./manifest/app.json):\n\n1. → **Create New App** → **From a manifest**.\n2. Pick your workspace, paste `manifest/app.json`, create.\n3. **Install to Workspace** and copy the **Bot User OAuth Token** (`xoxb-…`).\n4. From **Basic Information**, copy the **Signing Secret**.\n\nThe manifest requests the scopes Tier 2 needs: `app_mentions:read`,\n`channels:history`, `groups:history`, `im:history`, `mpim:history`,\n`chat:write`, `chat:write.public`, `chat:write.customize`,\n`reactions:write`; and subscribes to `app_mention` plus `message.*`.\n\n> `chat:write.customize` is what makes per-session `identity` overrides\n> visible. Without it, Slack ignores the username/avatar and posts fall\n> through under the default bot identity.\n\n### 2. Configure the city\n\nImport the pack in your city's `pack.toml`:\n\n```toml\n[imports.slack-channel]\nsource = \"../packs/slack-channel\"\n```\n\nProvide the adapter's environment:\n\n```sh\nSLACK_BOT_TOKEN=xoxb-... # from step 1\nSLACK_SIGNING_SECRET=... # from step 1\nSLACK_WORKSPACE_ID=T0123ABCD # your Slack team id\nGC_CITY_NAME= # the gc city to bridge into\nGC_CITY_PATH=/path/to/your/city # on-disk city root (for the registries)\n```\n\n`GC_CITY_PATH` is where the three registries live\n(`/.gc/slack-channel/`). Override the directory with\n`SLACK_CHANNEL_REGISTRY_DIR` if needed.\n\n### 3. Expose the events endpoint and start\n\nThe adapter listens for Slack events on public TCP (`LISTEN_PUBLIC`,\ndefault `0.0.0.0:8775`). Terminate TLS in front of it and give Slack a\npublic URL — [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) is the\neasy path:\n\n```sh\ntailscale funnel 8775\n```\n\nThen in **Event Subscriptions**, set the Request URL to\n`https:///slack/events`. Slack sends a one-time\n`url_verification` challenge, which the adapter answers automatically.\n\ngc supervises the adapter as a `proxy_process` service (named\n`slack-channel`); building the binary is a one-time `go build` in `adapter/`\n(see [Build](#build)). Start your city.\n\n## Usage walkthrough\n\n```sh\n# Bind a channel to the PL and reviewer sessions.\ngc slack-channel bind-room C0123 sess-pl sess-reviewer\n\n# Give the PL session its own identity (call once at session start).\ngc slack-channel identity --session sess-pl --as \"Gas City PL\" --avatar-emoji robot_face\n\n# Let humans address the mayor from any channel.\ngc slack-channel handle-alias --handle mayor --session sess-mayor\n\n# A human posts in C0123 → both bound sessions receive it. The PL replies\n# in-thread:\ngc slack-channel reply-current --body \"on it — see PR #42\" --thread-current\n\n# Drop a receipt on the message that just arrived.\ngc slack-channel react --emoji eyes\n\n# Unprompted status post into the bound channel.\ngc slack-channel publish --body \"nightly build is green\"\n```\n\n## Registries\n\nThe adapter owns three on-disk registries under\n`/.gc/slack-channel/`. JSON Schemas live in [`schema/`](./schema):\n\n| File | Schema | Contents |\n| --- | --- | --- |\n| `channel_mappings.json` | [channel_mappings.schema.json](./schema/channel_mappings.schema.json) | channel → bound sessions |\n| `identities.json` | [identities.schema.json](./schema/identities.schema.json) | session → username/avatar |\n| `handle_aliases.json` | [handle_aliases.schema.json](./schema/handle_aliases.schema.json) | handle → session |\n\nThe adapter is the only writer and reader; the verbs mutate them by POSTing\nto the adapter, never by touching the files directly.\n\n## Build\n\nThe adapter is a small multi-file Go module with no external dependencies:\n\n```sh\ncd adapter\ngo build -o gc-slack-channel-adapter ./...\ngo test ./...\n```\n\nThe built binary is git-ignored; the `[[service]]` block runs it in place.\n\n## Configuration reference\n\n| Variable | Required | Default | Purpose |\n| --- | :---: | --- | --- |\n| `SLACK_BOT_TOKEN` | ✓ | — | Bot token for `chat.postMessage` + `reactions.add`. |\n| `SLACK_SIGNING_SECRET` | ✓ | — | HMAC secret for verifying Slack requests. |\n| `SLACK_WORKSPACE_ID` | ✓ | — | Slack team id (extmsg account id + registry key). |\n| `GC_CITY_NAME` | ✓ | — | gc city to bridge into. |\n| `GC_CITY_PATH` | ✓* | — | City root; registry dir defaults under it. |\n| `SLACK_CHANNEL_REGISTRY_DIR` | | `/.gc/slack-channel` | Override the registry directory. |\n| `LISTEN_PUBLIC` | | `0.0.0.0:8775` | Public bind for `/slack/events` + `/slack/interactions`. |\n| `LISTEN_INTERNAL` | | `127.0.0.1:8776` | TCP bind for the verb endpoints when not a gc proxy_process. |\n| `REGISTER_ON_START` | | `true` | Self-register as an extmsg adapter on start. |\n| `SLACK_CHANNEL_INBOUND_TARGET` | | `mayor` | Fallback session for an unbound, unaliased `app_mention`. |\n| `SLACK_API_BASE` | | `https://slack.com/api` | Slack web API origin (override for relays/tests). |\n| `GC_API_BASE_URL` | | `http://127.0.0.1:9443` | gc API base. |\n\n\\* Either `GC_CITY_PATH` or `SLACK_CHANNEL_REGISTRY_DIR` must be set so the\nregistries have a home. `GC_SERVICE_SOCKET`, `GC_SERVICE_URL_PREFIX`, and\n`GC_API_BASE_URL` are injected by gc when the adapter runs as a\n`proxy_process` service.\n\n### Optional: interactive payloads\n\nTo enable the basic modal-button ack, turn on **Interactivity** in the Slack\napp and point its Request URL at `https:///slack/interactions`.\nThe adapter verifies the signature and acks with `200` so Slack dismisses\nthe interaction; it does no custom modal handling (that is Tier 3).\n\n## Security posture\n\nA few deliberate choices in the shipped manifest and adapter are worth\nunderstanding before you install:\n\n- **`chat:write.public` (manifest scope).** This lets the bot post to any\n *public* channel without first being invited — `publish-to-channel\n --channel ` reaches any public channel in the workspace, and a bound\n session can publish to a public channel it was bound to without a join\n step. The blast radius is \"every public channel in this workspace.\" If\n that is broader than you want, drop `chat:write.public` from\n [`manifest/app.json`](./manifest/app.json) and invite the bot to each\n channel it should post in (Slack then requires `conversations.join` /\n manual membership instead).\n- **`token_rotation_enabled: false` (manifest setting).** The adapter\n authenticates with a long-lived bot token (`SLACK_BOT_TOKEN`) rather than\n rotating, expiring tokens. Rotation would require the adapter to persist\n and refresh credentials; for a single-workspace Tier-2 bridge the static\n token is simpler and the token never leaves the adapter (verbs never see\n it). Treat the token as a secret, scope it to one workspace, and rotate it\n by hand if it is exposed. Enable rotation in the Slack app settings if your\n deployment policy requires it.\n- **Internal verb listener is loopback / dev-only.** When the adapter is\n *not* run as a gc `proxy_process` service (`GC_SERVICE_SOCKET` unset), the\n verb endpoints are served over plain TCP on `127.0.0.1:8776`\n (`LISTEN_INTERNAL`) with no authentication — anything that can reach that\n loopback port can mutate the registries. This mode is for local\n development only. In production gc runs the adapter as a `proxy_process`\n service and the verb endpoints bind a `0600` Unix-domain socket\n (`GC_SERVICE_SOCKET`) that only the gc supervisor can reach; do not expose\n `127.0.0.1:8776` beyond loopback.\n\n## Choosing a tier\n\n- **slack-mini** (Tier 1) — one outbound verb, `app_mention` → mayor. No\n registries.\n- **slack-channel** (Tier 2, this pack) — channel bindings, per-session\n identity, handle aliases. Single workspace, single rig.\n- **slack-full** (Tier 3) — multi-rig routing, slash commands, peer fanout,\n file upload, modal flows.\n\nThe bot token, signing secret, and workspace id carry over unchanged when\nyou move between tiers." + "content": "# slack-channel\n\n**Bridge Slack channels and DMs to your Gas City sessions.** Bind a channel\nto one or more gc sessions and every message there is delivered to them;\nreply, publish, and react back; give each session its own Slack identity;\naddress a session from any channel with `@handle`.\n\nslack-channel is **Tier 2** of the Slack pack family — the \"team channel ↔\nsession graph\" middle tier. It is built on slack-mini's single-file kernel\nand adds the state Tier 1 deliberately omits. It does **not** include the\nmulti-rig routing, slash-command intake, peer fanout, or file upload of\n`slack-full` (Tier 3).\n\n> Pick exactly one Slack tier per city. The tiers are alternatives, not\n> layers you stack.\n\n## What you get\n\n- **Bound channels:** `bind-room C0123 sess-a sess-b` — every human message\n in `C0123` is delivered to both sessions (not just `@`-mentions).\n- **Address by handle:** `@mayor: status?` in any channel routes to the\n session aliased to `mayor`.\n- **Reply / publish:** `reply-current` threads under the message that just\n arrived; `publish` posts into a session's bound channel;\n `publish-to-channel` posts anywhere by id.\n- **Per-session identity:** each session posts under its own username +\n avatar (`chat:write.customize`).\n- **React:** `react --emoji eyes` drops a receipt on the latest inbound.\n\n## Verbs\n\nAll verbs are `gc slack-channel `. Run any with `--help` for full\nflags.\n\n| Verb | Purpose |\n| --- | --- |\n| `bind-dm ` | Bind a DM to one or more sessions. |\n| `bind-room ` | Bind a channel to one or more sessions. |\n| `publish --body ` | Post into the current session's bound channel. |\n| `publish-to-channel --channel --body ` | Post into any channel by id. |\n| `reply-current --body [--thread-current]` | Reply into the session's latest inbound conversation. |\n| `react [--emoji ]` | React on the session's latest inbound message. |\n| `identity --as [--avatar-emoji ]` | Set the session's Slack username + avatar. |\n| `handle-alias --handle --session ` | Map `@` to a session. |\n\n## Install\n\n### Choose a transport\n\nInbound messages reach the adapter one of two ways. Pick one before you\ncreate the app — the choice is baked into the app manifest.\n\n| | **Socket Mode** | **HTTP (Events API)** |\n| --- | --- | --- |\n| Public ingress | none | public HTTPS URL required |\n| How events arrive | outbound WebSocket the adapter opens to Slack | Slack POSTs to your Request URL |\n| Credentials | app-level token (`xapp-…`) | signing secret |\n| Manifest | [`manifest/app-socket.json`](./manifest/app-socket.json) | [`manifest/app.json`](./manifest/app.json) |\n| Interactivity (opt-in) | not supported | `/slack/interactions` |\n\n**Socket Mode is the right default if you cannot expose an inbound port** —\nbehind a corporate firewall, on a laptop, or anywhere a tunnel is blocked.\nIt needs only outbound HTTPS to `slack.com`. Bindings, handle aliases,\nidentities, and every outbound verb behave identically on both.\n\nThe one thing Socket Mode cannot carry is interactivity. Tier 2 ships it\ndisabled, so this only matters if you deliberately turned on\n`/slack/interactions` — that half still needs a public URL.\n\n### 1. Create the Slack app\n\nCreate an app from the shipped manifest — `manifest/app-socket.json` for\nSocket Mode, `manifest/app.json` for HTTP:\n\n1. → **Create New App** → **From a manifest**.\n2. Pick your workspace, paste the manifest, create.\n3. **Install to Workspace** and copy the **Bot User OAuth Token** (`xoxb-…`).\n4. From **Basic Information**:\n - **Socket Mode:** under **App-Level Tokens**, **Generate Token and\n Scopes**, add the **`connections:write`** scope, and copy the token\n (`xapp-…`). App-level tokens cannot be declared in a manifest, so this\n step is manual.\n - **HTTP:** copy the **Signing Secret**.\n\nThe manifest requests the scopes Tier 2 needs: `app_mentions:read`,\n`channels:history`, `groups:history`, `im:history`, `mpim:history`,\n`chat:write`, `chat:write.public`, `chat:write.customize`,\n`reactions:write`; and subscribes to `app_mention` plus `message.*`.\n\n> `chat:write.customize` is what makes per-session `identity` overrides\n> visible. Without it, Slack ignores the username/avatar and posts fall\n> through under the default bot identity.\n\n### 2. Configure the city\n\nImport the pack into your city:\n\n```sh\ngc import add https://github.com/gastownhall/gascity-packs.git//slack-channel\n```\n\nThat writes the import and pins the release in `packs.lock`. The same import\nby hand in `pack.toml`, followed by `gc import install`:\n\n```toml\n[imports.slack-channel]\nsource = \"https://github.com/gastownhall/gascity-packs.git//slack-channel\"\n```\n\n`gc pack registry show slack-channel` prints the registry entry and its releases.\nTo work on the pack from a local checkout, point `source` at the path\ninstead:\n\n```toml\n[imports.slack-channel]\nsource = \"../gascity-packs/slack-channel\"\n```\n\nProvide the adapter's environment:\n\n```sh\nSLACK_BOT_TOKEN=xoxb-... # from step 1\nSLACK_APP_TOKEN=xapp-... # Socket Mode only — selects the transport\nSLACK_SIGNING_SECRET=... # HTTP only\nSLACK_WORKSPACE_ID=T0123ABCD # your Slack team id\nGC_CITY_NAME= # the gc city to bridge into\nGC_CITY_PATH=/path/to/your/city # on-disk city root (for the registries)\n```\n\nSetting `SLACK_APP_TOKEN` is the whole switch: the adapter runs Socket\nMode, never binds `LISTEN_PUBLIC`, and stops requiring\n`SLACK_SIGNING_SECRET` (Socket Mode has no request signatures to verify).\n\n`GC_CITY_PATH` is where the three registries live\n(`/.gc/slack-channel/`). Override the directory with\n`SLACK_CHANNEL_REGISTRY_DIR` if needed.\n\n### 3. Start\n\n**Socket Mode — nothing to expose.** The adapter dials out to Slack, so\nthere is no listener to publish, no TLS to terminate, and no Request URL to\nregister. Start your city; the log line to look for is\n`socket mode: connected`.\n\n**HTTP — expose the events endpoint.** The adapter listens for Slack events\non public TCP (`LISTEN_PUBLIC`, default `0.0.0.0:8775`). Terminate TLS in\nfront of it and give Slack a public URL —\n[Tailscale Funnel](https://tailscale.com/kb/1223/funnel) is the easy path:\n\n```sh\ntailscale funnel 8775\n```\n\nThen in **Event Subscriptions**, set the Request URL to\n`https:///slack/events`. Slack sends a one-time\n`url_verification` challenge, which the adapter answers automatically.\n\ngc supervises the adapter as a `proxy_process` service (named\n`slack-channel`); the service command is `adapter/run.sh`, which builds the\nbinary from source on first start (see [Build](#build)). Start your city.\n\n## Usage walkthrough\n\n```sh\n# Bind a channel to the PL and reviewer sessions.\ngc slack-channel bind-room C0123 sess-pl sess-reviewer\n\n# Give the PL session its own identity (call once at session start).\ngc slack-channel identity --session sess-pl --as \"Gas City PL\" --avatar-emoji robot_face\n\n# Let humans address the mayor from any channel.\ngc slack-channel handle-alias --handle mayor --session sess-mayor\n\n# A human posts in C0123 → both bound sessions receive it. The PL replies\n# in-thread:\ngc slack-channel reply-current --body \"on it — see PR #42\" --thread-current\n\n# Drop a receipt on the message that just arrived.\ngc slack-channel react --emoji eyes\n\n# Unprompted status post into the bound channel.\ngc slack-channel publish --body \"nightly build is green\"\n```\n\n## Registries\n\nThe adapter owns three on-disk registries under\n`/.gc/slack-channel/`. JSON Schemas live in [`schema/`](./schema):\n\n| File | Schema | Contents |\n| --- | --- | --- |\n| `channel_mappings.json` | [channel_mappings.schema.json](./schema/channel_mappings.schema.json) | channel → bound sessions |\n| `identities.json` | [identities.schema.json](./schema/identities.schema.json) | session → username/avatar |\n| `handle_aliases.json` | [handle_aliases.schema.json](./schema/handle_aliases.schema.json) | handle → session |\n\nThe adapter is the only writer and reader; the verbs mutate them by POSTing\nto the adapter, never by touching the files directly.\n\n## Build\n\nThe adapter is a small multi-file Go module with no external dependencies:\n\n```sh\ncd adapter\ngo build -o gc-slack-channel-adapter ./...\ngo test ./...\n```\n\nThe built binary is git-ignored. The `[[service]]` block runs `adapter/run.sh`,\nwhich execs the binary when it exists and otherwise builds it from the\nsources next to it first, so a pack pin bump (`gc import install`\nre-materializes the pack from git only) no longer strands the service. The\nfirst build runs inside gc's readiness window, so expect one or more\nrestarts until the Go build cache is warm; a Go toolchain must be installed.\n\nThe built binary is git-ignored; the `[[service]]` block runs it in place.\n\n## Configuration reference\n\n| Variable | Required | Default | Purpose |\n| --- | :---: | --- | --- |\n| `SLACK_BOT_TOKEN` | ✓ | — | Bot token for `chat.postMessage` + `reactions.add`. |\n| `SLACK_SIGNING_SECRET` | HTTP only | — | HMAC secret for verifying Slack requests. Required unless `SLACK_APP_TOKEN` is set. |\n| `SLACK_APP_TOKEN` | Socket Mode | — | App-level token (`xapp-…`) with `connections:write`. Setting it selects Socket Mode: no public listener, no signing secret. |\n| `SLACK_WORKSPACE_ID` | ✓ | — | Slack team id (extmsg account id + registry key). |\n| `GC_CITY_NAME` | ✓ | — | gc city to bridge into. |\n| `GC_CITY_PATH` | ✓* | — | City root; registry dir defaults under it. |\n| `SLACK_CHANNEL_REGISTRY_DIR` | | `/.gc/slack-channel` | Override the registry directory. |\n| `LISTEN_PUBLIC` | | `0.0.0.0:8775` | Public bind for `/slack/events` + `/slack/interactions`. Not bound in Socket Mode. |\n| `LISTEN_INTERNAL` | | `127.0.0.1:8776` | TCP bind for the verb endpoints when not a gc proxy_process. |\n| `REGISTER_ON_START` | | `true` | Self-register as an extmsg adapter on start. |\n| `SLACK_CHANNEL_INBOUND_TARGET` | | `mayor` | Fallback session for an unbound, unaliased `app_mention`. |\n| `SLACK_API_BASE` | | `https://slack.com/api` | Slack web API origin (override for relays/tests). |\n| `GC_API_BASE_URL` | | `http://127.0.0.1:9443` | gc API base. |\n\n\\* Either `GC_CITY_PATH` or `SLACK_CHANNEL_REGISTRY_DIR` must be set so the\nregistries have a home. `GC_SERVICE_SOCKET`, `GC_SERVICE_URL_PREFIX`, and\n`GC_API_BASE_URL` are injected by gc when the adapter runs as a\n`proxy_process` service.\n\n### Optional: interactive payloads\n\nTo enable the basic modal-button ack, turn on **Interactivity** in the Slack\napp and point its Request URL at `https:///slack/interactions`.\nThe adapter verifies the signature and acks with `200` so Slack dismisses\nthe interaction; it does no custom modal handling (that is Tier 3).\n\n## Security posture\n\nA few deliberate choices in the shipped manifest and adapter are worth\nunderstanding before you install:\n\n- **`chat:write.public` (manifest scope).** This lets the bot post to any\n *public* channel without first being invited — `publish-to-channel\n --channel ` reaches any public channel in the workspace, and a bound\n session can publish to a public channel it was bound to without a join\n step. The blast radius is \"every public channel in this workspace.\" If\n that is broader than you want, drop `chat:write.public` from\n [`manifest/app.json`](./manifest/app.json) and invite the bot to each\n channel it should post in (Slack then requires `conversations.join` /\n manual membership instead).\n- **`token_rotation_enabled: false` (manifest setting).** The adapter\n authenticates with a long-lived bot token (`SLACK_BOT_TOKEN`) rather than\n rotating, expiring tokens. Rotation would require the adapter to persist\n and refresh credentials; for a single-workspace Tier-2 bridge the static\n token is simpler and the token never leaves the adapter (verbs never see\n it). Treat the token as a secret, scope it to one workspace, and rotate it\n by hand if it is exposed. Enable rotation in the Slack app settings if your\n deployment policy requires it.\n- **Events from another workspace are dropped, on both transports.** Neither\n transport establishes which workspace an event came from: Socket Mode has\n no signature at all, and an HTTP request signature proves only that Slack\n sent the event *for this app*, not that it came *from this team*. An app\n installed in a second workspace would otherwise have that workspace's\n messages filed under `SLACK_WORKSPACE_ID` and matched against this\n workspace's channel bindings, so an event whose `team_id` is set and does\n not match is dropped with a log line. Run one workspace per install.\n- **Internal verb listener is loopback / dev-only.** When the adapter is\n *not* run as a gc `proxy_process` service (`GC_SERVICE_SOCKET` unset), the\n verb endpoints are served over plain TCP on `127.0.0.1:8776`\n (`LISTEN_INTERNAL`) with no authentication — anything that can reach that\n loopback port can mutate the registries. This mode is for local\n development only. In production gc runs the adapter as a `proxy_process`\n service and the verb endpoints bind a `0600` Unix-domain socket\n (`GC_SERVICE_SOCKET`) that only the gc supervisor can reach; do not expose\n `127.0.0.1:8776` beyond loopback.\n\n## Choosing a tier\n\n- **slack-mini** (Tier 1) — one outbound verb, `app_mention` → mayor. No\n registries.\n- **slack-channel** (Tier 2, this pack) — channel bindings, per-session\n identity, handle aliases. Single workspace, single rig.\n- **slack-full** (Tier 3) — multi-rig routing, slash commands, peer fanout,\n file upload, modal flows.\n\nThe bot token, signing secret, and workspace id carry over unchanged when\nyou move between tiers." }, "og_image": "/og/slack-channel.svg", - "latest": "0.1.1", + "latest": "0.1.2", "releases": [ { "version": "0.1.0", @@ -493,6 +501,14 @@ "hash": "sha256:e8da9a9dab6e6d3ce8a3cded94104a1c64d88f39130e63b09653ce3e7457dd36", "description": "Ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark schema test executable to align release hash with materialized modes.", "withdrawn": false + }, + { + "version": "0.1.2", + "ref": "main", + "commit": "ea4066504ef753c0b4e8bcc73aea2d447c964969", + "hash": "sha256:a96786bd538a592dc7eb259adefc2f5d731aafc2724582133bab777a89c00e5b", + "description": "Socket Mode transport, adapter registration retry during city startup, a checked-in run.sh that rebuilds a missing adapter binary, and install docs for the git import.", + "withdrawn": false } ] }, @@ -505,13 +521,13 @@ "description": "Slack services, commands, and adapter integration for Gas City.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/slack-full", "source_kind": "git", - "search_text": "slack-full gascity-packs maintained gas city slack services, commands, and adapter integration for gas city. https://github.com/gastownhall/gascity-packs/tree/main/slack-full git # slack-full (v0.0.1 preview — scaffold) a slack provider extension for gas city. modeled directly on the upstream `discord` pack (https://github.com/gastownhall/gascity-packs/tree/main/discord) so the same primitives can be ported one at a time. this pack lives at `slack-full/` in the [`gastownhall/gascity-packs`](https://github.com/gastownhall/gascity-packs) catalog. a city opts in by path-importing its `pack.toml` (see \"install\" below). ## tiering `slack-full` is **tier 3** of the slack pack family — workspace-grade orchestration. it is the original, full-surface pack; the two smaller tiers were later extracted from it for cities that need less. - **[slack-mini](../slack-mini)** (tier 1) — single-file adapter, one outbound verb (`gc slack-mini post-message`), `app_mention` → mayor. no channel bindings, no state. reach for it when you only need the mayor to speak into one channel. - **[slack-channel](../slack-channel)** (tier 2) — the \"team channel ↔ session graph\" middle tier: channel bindings, per-session identities, handle aliases, reactions, threaded replies. no multi-rig routing, slash commands, peer fanout, or launcher mode. - **slack-full** (tier 3, this pack) — workspace-grade orchestration: multi-rig routing, slash commands + interactions, peer fanout, launcher mode, bidirectional file attachments, and per-session identity overrides. ### choosing a tier - need the mayor to post status into a single channel and nothing else? → **slack-mini**. - need a handful of named sessions sharing one or more channels with distinct identities, but no slash commands or cross-rig routing? → **slack-channel**. - need slash commands, interactive modals/buttons, peer fanout across a room, launcher-mode session spawning, or multi-rig channel routing? → **slack-full** (this pack). see the [slack pack tiering design memo](../docs/design/slack-pack-tiering.md) for the full rationale. > **scope: not yet at parity with the discord pack.** the discord pack > ships ~350k loc of provider-agnostic python state-machine logic. > slack pack is a feature-by-feature port; today's surface is enough > to run a multi-session oversight loop end-to-end (dms + rooms + > peer fanout + identity overrides + bidirectional file > attachments), but several discord-pack features are still missing > (see \"not yet implemented\" below). if you need parity today, use > the discord pack. ## status implemented: - [x] `gc slack bind-dm` — bind a slack dm channel to one named session - [x] `gc slack bind-room` — bind a room to multiple sessions; flags `--enable-peer-fanout`, `--allow-untargeted-publication`, `--max-peer-triggered-publishes`, `--max-total-peer-deliveries`, `--default-handle`, `--handle handle=session` (creates a launcher-mode group + participants under the hood) - [x] `gc slack reply-current` — reply to the latest slack event in the current session, by default through gc's `/extmsg/outbound` so transcript recording + peer fanout fire (`--via adapter` keeps the old direct-to-adapter path for diagnostics) - [x] `gc slack publish` — publish to a session's saved binding (target session required, no event-scan fallback — fail-fast when the session has no active binding) - [x] `gc slack publish-to-channel` — publish to an arbitrary channel id (no session binding required; useful for one-shot ops posts) - [x] `gc slack status` — read-only diagnostics (adapters, bindings, recent traffic). `--session sid` for one-session detail, `--since 5m` for a time window, `--json` for scripting. - [x] `gc slack react` — add an emoji reaction to a slack message - [x] `gc slack identity` — register/unregister a per-session `chat:write.customize` identity (display name + icon) so each bound session posts under its own persona - [x] `gc slack handle-alias` — register/unregister a cross-channel `@handle` → session-id alias used by the address-by-handle protocol - [x] `gc slack upload` — bidirectional file attachments (`/publish-file` outbound + auto-download of inbound files into `$inbound_file_store//-`, scrubbed by an in-process retention janitor) - [x] `template-fragments/slack-v0.template.md` — composable prompt fragment for any agent in a slack-bound session - [x] pack-owned intake service (`[[service]]` proxy_process). phase a: adapter is the same go binary, but gc supervises it via uds for `/publish` while the public slack webhook still terminates at adapter tcp `:8775` (funnel unchanged). see \"adapter as a proxy_process service\" below for the cutover. slash-command / launcher / projection surface (operator cli verbs): - [x] `gc slack import-app` — register a slack app manifest with the gc city ([`manifest/readme.md`](./manifest/readme.md#importing-into-gc-gc-slack-import-app)) - [x] `gc slack map-channel` — bind a slack channel to a session; backs the adapter's `/slack/interactions` slash-command dispatcher. (note: the legacy `--rig` flag on this verb is deprecated as of gc-cby.25; use `gc slack map-rig` for rig→channel bindings. the flag still works for back-compat and emits a stderr deprecation warning.) - [x] `gc slack map-rig` — bind a rig to a set of channels as the fall-through default for slash-command resolution. per-channel `map-channel --session` bindings override rig defaults; channel mapping wins. cross-store conflict detection refuses contradictory writes in either direction. `--remove` drops the entire rig record; `--remove-channels c1,c2` drops just the listed channels (the record itself is deleted if the set becomes empty). both removal paths are idempotent. - [x] `gc slack sync-commands` — register/refresh the city's slash commands against the slack app (read by the adapter's `/slack/interactions` dispatcher). - [x] `gc slack sync-subteam-aliases` — reconcile `/.gc/slack/subteam-aliases.json` (the slack user group (\"subteam\") id → gc handle map) with the workspace's live user groups via slack `usergroups.list`. like `sync-commands`, it diffs live against on-disk and supports `--dry-run` and `--output text|json`; unlike it, the write is a non-destructive merge — existing entries are preserved, handles are refreshed where the live name differs, new user groups are added, and entries with no matching live group are kept and reported as \"local-only\" rather than dropped. the bot token (`--token` / `$slack_bot_token`) must carry the `usergroups:read` scope; without it the verb reports a readable `missing_scope` error and writes nothing. after a write it prints the same sighup reminder as the other registry-writing verbs. - [x] `gc slack post-message` — post (or `--update `) a block kit message to a channel via `chat.postmessage`/`chat.update`. the one-shot verb is shipped; automatic workflow-status projection from gc events is not yet wired. - [x] `gc slack retry-peer-fanout` (walks `extmsg.peer_fanout_failed` events, dedupes against successful `extmsg.peer_fanout_retried` events, re-issues each notification via the gc retry endpoint with a small cooldown between attempts) not yet implemented (planned): - [ ] `gc slack enable-room-launch` — the cli verb writes the `(workspace_id, channel_id) → pool_template` mapping today, but the adapter-side `@@` thread-scoped session spawn is still a stub: an `@@` post in an enabled channel currently gets an ephemeral \"launcher not yet available\" reply rather than spawning a session (tracked under the gc-cby epic). the adapter exposes `post /slack/interactions` (hmac-verified) for slash-command, `block_actions`, and `view_submission` dispatch. `block_actions` (button clicks, select-menu finalizations, datepickers, etc.) routes through the same channel-binding flow as slash commands: the action's originating channel (`payload.channel.id` falling back to `payload.container.channel_id`) must be bound via `gc slack map-channel` (session target) or `gc slack map-rig` (rig target — the rig branch is recorded but routing is deferred to gc-cby.18). `view_submission` (modal submits) carries no channel context, so the modal opener must set `view.private_metadata` to the json string `{\"session_id\":\"\"}` when calling `views.open`/`views.push`. on submit, the adapter strict-decodes that field (extra keys rejected, session_id length-capped) and posts a system-reminder to that session describing the `callback_id` plus `view.state.values`. any decode failure responds `{\"response_action\":\"clear\"}` so slack closes the modal stack and the user knows the submit did not process. the adapter's workspace gate (`slack_workspace_id` / `cfg.accountid`) applies on both branches — `payload.team.id` must match. `response_url` (valid ~30 minutes / 5 uses on `block_actions`) is forwarded to the agent in the system-reminder; persistent storage of `response_url` for later use is out of scope. other slack interaction types (`shortcut`, `message_action`, `view_closed`, `block_suggestion`) return an ephemeral \"unsupported interaction type\" reply — they need separate routing logic and are tracked under the gc-cby epic. ## binaries this pack ships **two** go binaries, each with its own `go.mod` so either can travel intact when the pack is mirrored upstream: - **adapter** — `slack-full/adapter/gc-slack-adapter`, built from `adapter/main.go`. the public-facing webhook receiver (slack → `:8775` over tailscale funnel) and outbound publisher (`/publish` over the controller-managed uds). long-running; gc supervises it via the `[[service]]` block in `pack.toml`. - **operator cli** — `slack-full/cli/gc-slack-cli`, built from `cli/main.go` + `cli/cmd/`. backs the `gc slack ` verb surface (import-app, map-channel, map-rig, sync-commands, sync-subteam-aliases, enable-room-launch, post-message). one-shot per invocation; not supervised. the pack's `commands/.sh` wrappers exec it at `$gc_pack_dir/cli/gc-slack-cli` so operator command-line ergonomics stay identical to the pre-relocation gc-binary verbs. build commands and ci for both binaries are documented in [contributing.md](./contributing.md#build-flow). ## architecture (current) the go adapter at `slack-full/adapter/gc-slack-adapter` (built from `adapter/main.go` colocated with this pack) is the public-facing webhook receiver and outbound publisher. this pack adds cli surface around it: `bind-dm` writes to gc's `/extmsg/bind` and to the pack's local config; `reply-current` reads recent gc events to find the conversation, then posts to gc's `/extmsg/outbound` (which calls the registered http adapter's `/publish` endpoint internally and emits `extmsgoutbound` so peer fanout fires for bind-room sessions). `--via adapter` is available for adapter-only diagnostics that bypass gc. ``` ┌──── public ────┐ slack ──hmac──▶ go adapter :8775 ──▶ gc /extmsg/inbound go adapter :8766 ◀── gc /extmsg/outbound ◀── gc slack reply-current ◀── (--via adapter) ─────── gc slack reply-current └────────────────┘ ``` the `:8775` public port above is the reference funnel-fronted deployment (`listen_public=:8775`, with a tailscale funnel rule `:443 → :8775`). the bare adapter default is `:8765` — see the `listen_public` row in \"adapter env contract\" below. the adapter's `/publish` endpoint **requires session attribution**: every request must carry a `session_id` (or, for older gc binaries, the legacy `metadata.source_session_id`). a request with neither is rejected with http 400 and nothing is posted to slack: ```json {\"error\": \"publish requires session attribution: provide session_id or metadata.source_session_id\"} ``` this fails closed against identity-less channel-root posts under the default bot identity. system/bot status notifications that intentionally post as the bot go through `gc slack post-message` (direct to slack `chat.postmessage`), which bypasses `/publish` entirely and is unaffected by this guard. ## install ```toml # city.toml [imports.slack-full] source = \"/path/to/gascity-packs/slack-full\" ``` then `gc reload` (or wait for the supervisor to pick up the change). verify the commands appear: ``` gc slack --help gc slack bind-dm --help gc slack reply-current --help ``` ## verify assuming the adapter is up and the four must-set vars (`slack_workspace_id`, `slack_bot_token`, `slack_signing_secret`, `gc_city_name`) are exported (sourced from `${xdg_config_home:-$home/.config}/gc-slack-adapter/env` — see \"adapter env contract\" below): ``` # bind a dm channel to a session. replace d0xxxxxxxxx with the dm # channel id slack assigned to your bot's im with the human user. gc slack bind-dm d0xxxxxxxxx my-session.cos # from inside that session (or any session that has seen recent # extmsg.inbound on a slack conversation): echo \"*my-session.cos:* ack via slack pack\" > /tmp/reply.txt gc slack reply-current --body-file /tmp/reply.txt ``` the reply should land in your slack dm. to bind a room (public or private channel) to multiple sessions so that two or more agents are visible peers and a human can join the conversation: ``` gc slack bind-room c0xxxxxxxxx \\ my-pack.session-a my-pack.session-b \\ --enable-peer-fanout \\ --binding-owner my-pack.session-b ``` both sessions then receive an inbound system reminder for every human message in the channel; `extmsg.inbound` events list both as conversation members. when the publishing session calls `gc slack reply-current` (default `--via gc`), gc records the publish in the conversation transcript and fans out a peer-publication reminder to the other bound sessions so they see what their peer just said. `--binding-owner session` is what makes outbound publishes (and therefore `gc slack reply-current --via gc`) actually work. without it, peer fanout still fires on inbound, but `/extmsg/outbound` has no `sessionbindingrecord` to resolve the conversation through and the publish is rejected. the owner must be one of the participants — prefer the session that \"owns\" the room from gc's perspective. pass the gc session id (e.g. `gc-77139`) when alias resolution semantics matter; for stable named sessions, the alias works too. ## adapter as a proxy_process service phase a of the in-pack adapter (tracked as bd `gc-5rz`) lets gc supervise the adapter as part of the city's services. the adapter binds a unix domain socket for the `/publish` endpoint that gc reaches via the extmsg http adapter, and gc reverse-proxies `/svc/slack/*` to that uds. the public slack webhook (`/slack/events`) still terminates at the adapter's tcp `:8775` so tailscale funnel and slack's signing secret verification are unchanged. the same binary runs in both modes — the legacy `nohup ./run.sh` deployment is preserved as a rollback target. ### what gc injects vs. what stays in the env file `proxy_process` injects the controller-managed env at start time: - `gc_service_name=slack` - `gc_service_socket=/tmp/gcsvc-//slack-*.sock` - `gc_service_url_prefix=/svc/slack` - `gc_service_state_root=.../.gc/services/slack` - `gc_service_run_root=.../.gc/services/slack/run` note: `gc_api_base_url` and `gc_city_name` are not injected by the controller for `proxy_process` services — they must be present in the env file you source before `gc start`, so the supervisor inherits them and passes them down to the spawned adapter. when `gc_service_socket` is set, the adapter: - skips its `listen_internal` tcp listener and binds the uds instead; - computes its self-registration `callbackurl` as `$gc_api_base_url + $gc_service_url_prefix` (gc's extmsg http adapter appends `/publish` itself when calling out, so the registered base url must not include `/publish`); - still binds public tcp for `/slack/events`; - still serves `/healthz` on the uds so the controller's `health_path` probe succeeds. ### adapter env contract the full adapter env contract — what the binary at `slack-full/adapter/main.go` reads — is enumerated in the package docstring at the top of that file. summary: **must-set** (no default; adapter exits at startup if missing): | var | purpose | | ---------------------- | ------------------------------------------------------------------ | | `slack_workspace_id` | slack team id (e.g. `t0xxxxxxxxx`). | | `slack_bot_token` | `xoxb-…` token. scopes: `chat:write`, `reactions:write`, `files:write`, and `chat:write.customize` for per-session identity overrides. | | `slack_signing_secret` | used to verify hmac signatures on `/slack/events` requests. | | `gc_city_name` | gc city the adapter posts inbound + session-message traffic to. matches `[workspace].name` in `city.toml`. no fallback default — the adapter fails fast rather than silently route to a wrong city. | **optional override** (sane default; set to override): | var | default | purpose | | ------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------- | | `listen_public` | `:8765` | public listener for `/slack/events` (bind `0.0.0.0` if fronted by a tunnel). | | `listen_internal` | `127.0.0.1:8766` | loopback listener for `/publish`. ignored under proxy_process mode. | | `internal_callback_url` | `http://127.0.0.1:8766` | url advertised to gc at self-registration. ignored under proxy_process mode. | | `gc_api_base_url` | `http://127.0.0.1:9443` | base url for gc's http api. | | `adapter_provider` | `slack` | provider name in conversation refs + adapter registration. | | `register_on_start` | `true` | set `false` to skip `/extmsg/adapters` registration (tests, diagnostics). | | `handle_prefix` | `@` | leading address token for keyword routing. empty disables routing. | | `identity_store_path` | `/tmp/gc-slack-adapter/identities.json` | json file backing the per-session `chat:write.customize` identity registry. | | `handle_alias_store_path` | `/tmp/gc-slack-adapter/handle-aliases.json` | json file backing the cross-channel handle → session-id alias registry. | | `slack_subteam_alias_file` | `/.gc/slack/subteam-aliases.json` (or `/tmp/gc-slack-adapter/subteam-aliases.json`) | json map of slack user group (\"subteam\") ids → gc handles, hand-edited or written by `gc slack sync-subteam-aliases`. required to route the unlabeled `` mention shape; the labeled `` / `` shape is gated by `handle_alias_store_path` instead. read-only at runtime; sighup or restart to reload. | | `slack_user_alias_file` | `/.gc/slack/slack-user-aliases.json` (or `/tmp/gc-slack-adapter/slack-user-aliases.json`) | json map of bare gc handle → raw slack target id — a user (`uxxxx`/`wxxxx`) or user group (`sxxxx`). the **outbound** inverse of `slack_subteam_alias_file`: `/publish` rewrites `@handle` body tokens to slack mention syntax (`<@uxxxx>` / ``) so they render as clickable, notifying mentions instead of literal text. only handles present in the map are rewritten — unmapped handles stay literal (fail-safe, no surprise pings). the `@` is only treated as a mention at a left word boundary, so email-like `user@host` strings are left intact. hand-edited (operator-curated allowlist); read-only at runtime, sighup or restart to reload. | | `inbound_file_store` | `/tmp/gc-slack-adapter/inbound` | directory for downloaded inbound slack file attachments. | | `inbound_file_ttl` | `168h` (7 days) | janitor retention. `0` disables sweeping. | | `inbound_file_sweep_interval` | `1h` | janitor scan period. `0` disables sweeping. | | `slack_dispatch_concurrency` | `50` | cap on in-flight inbound-dispatch goroutines (slash-command, slack-event, alias-resolved). on saturation the adapter drops the dispatch with a `dispatch queue full` log line; the inbound post itself is not affected. must be a positive integer; 0/negative/non-numeric values fail at startup. (sec-s-04) | **permissions:** `identity_store_path`, `handle_alias_store_path`, and `inbound_file_store` are written with `0o700` directories and `0o600` files so contents are readable only by the adapter's uid. on startup the adapter additionally tightens any pre-existing files/directories that are looser (one-shot migration for legacy `/tmp/gc-slack-adapter/` trees from earlier versions). operators using a custom-mode parent (e.g. setgid for shared-group access) should set the perms before adapter start; the tightener preserves setuid/setgid/sticky bits and never loosens an operator-tighter mode. for multi-tenant hosts, override these paths to a host-private location explicitly created with `0o700`. the proxy_process unix domain socket (`gc_service_socket`) is also chmod'd to `0o600` after bind as defense-in-depth on top of its `0o700` controller-managed parent directory at `/tmp/gcsvc-//`. **controller-injected** (proxy_process mode only — gc sets these when the adapter runs as a `[[service]]`; do not set them by hand): `gc_service_name`, `gc_service_socket`, `gc_service_url_prefix`, `gc_service_state_root`, `gc_service_run_root`. see \"what gc injects vs. what stays in the env file\" above. ### sighup-driven reload five of the adapter's registries are written by `gc slack` cli commands and read by the adapter at startup: | registry | file | cli writer | | -------------------- | ----------------------------- | ------------------------------- | | apps | `apps.json` | `gc slack import-app` | | channel mappings | `channel_mappings.json` | `gc slack map-channel` | | rig mappings | `rig_mappings.json` | `gc slack map-rig` | | room launch mappings | `room_launch_mappings.json` | `gc slack enable-room-launch` | | subteam aliases | `subteam-aliases.json` | `gc slack sync-subteam-aliases` | send `sighup` to the running adapter to pick up cli-driven changes without a full restart: ``` pkill -hup gc-slack-adapter ``` reload is all-or-nothing across the five files — a single parse failure (corrupt json, unknown `target_kind`, missing required field, file >10 mib) aborts the cycle with the live in-memory state untouched. errors are logged at warn; the adapter keeps serving. a missing file is a **no-op** (live state is preserved). to clear a registry, write an empty json object instead of removing the file: ``` echo '{}' > /.gc/slack/channel_mappings.json pkill -hup gc-slack-adapter ``` the other three registries — `identity_store_path`, `handle_alias_store_path`, and the thread-session store — are **adapter-owned**: written in-process by the adapter itself (via `/identity`, `/handle-alias`, and the launcher), not by the cli, so they do not participate in sighup reload. do not hand-edit them while the adapter is running — it rewrites them from its in-memory state at will, so manual edits are silently overwritten. the five files in the table above are the operator-editable surface; everything else under the store paths belongs to the adapter. `apps.json` is the one file written from **both** sides (the cli's `import-app` and the adapter's oauth callback). the adapter generation-stamps its reload snapshots so a sighup racing an oauth callback re-stages instead of rolling the in-memory view back to the pre-callback state; if the log shows `apps registry commit skipped`, re-send the sighup. **consumer-specific** (referenced by deployment scripts and prompts in sibling tooling, not by the adapter binary): variables consumed by `deliver-rollup.sh`, `resolve_rig_channel.py`, etc., live with the consumer pack and are documented there. the adapter has no opinion on what handles map to which sessions — that's `/handle-alias` registry content, set by the consumer at deploy time. ### slack secrets — placement recommended placement for the four must-set vars (and any overrides): `${xdg_config_home:-$home/.config}/gc-slack-adapter/env` (`0600` perms). source the file before `gc start` so the adapter inherits the env via `os.environ()`. under systemd-managed deployments, drop a unit override that points `environmentfile=-…/gc-slack-adapter/env` at the same path so the supervisor passes the env to its `proxy_process` children. phase b will move the env-file path into pack config; not yet wired. ``` slack_workspace_id=t0xxxxxxxxx slack_bot_token=xoxb-... slack_signing_secret=... gc_city_name= ``` ### cutover sequence ``` # 1. build the adapter binary in place (source colocated with the pack) ( cd slack-full/adapter && go build -o gc-slack-adapter ) # 2. source the secrets so the supervisor inherits them set -a; source \"${xdg_config_home:-$home/.config}/gc-slack-adapter/env\"; set +a # 3. stop any manually-managed adapter that may still be running pkill -f gc-slack-adapter || true # 4. reload the city so the [[service]] block from slack-full registers gc reload # or: gc supervisor reload # 5. verify the service is ready gc service list # expect: slack proxy_process ready curl --unix-socket \"$(gc service show slack --json | jq -r .socket)\" \\ http://x/healthz # expect: 200, first line \"ok\", # then dispatch_dropped_total=n # (inbound events dropped because # the dispatch pool was saturated; # a growing n means raise # slack_dispatch_concurrency) # 6. verify outbound publish through gc (replace the placeholders). # gc_api_base_url defaults to http://127.0.0.1:8372 on a single-host # deployment; gc_city_name is the directory-name of your city. curl -s -x post -h \"content-type: application/json\" -h \"x-gc-request: cutover\" \\ -d '{\"session_id\":\"\",\"conversation\":{\"scope_id\":\"\",\"provider\":\"slack\",\"account_id\":\"t0xxxxxxxxx\",\"conversation_id\":\"d0xxxxxxxxx\",\"kind\":\"dm\"},\"text\":\"*cutover:* hello\"}' \\ \"${gc_api_base_url:-http://127.0.0.1:8372}/v0/city/${gc_city_name:-}/extmsg/outbound\" \\ | jq '.receipt.delivered' # expect: true # 7. verify inbound — send a slack dm to the bot, then: gc events --city \"${gc_city_name:-}\" --type extmsg.inbound --since 2m ``` rollback: remove (or comment out) the `[[service]]` block in `pack.toml`, `gc reload`, then restart the manual adapter via the legacy script: ``` ( cd slack-full/adapter \\ && nohup ./run.sh > /tmp/gc-slack-adapter/run.log 2>&1 & disown ) ``` the adapter ignores `$gc_service_socket` when unset and falls back to tcp-only mode, so the same binary serves both deployments. ### known foot-guns - **two adapters running.** if you forget step 3 and the manual adapter stays up while gc starts the proxy_process one, both will call `/extmsg/adapters` to register. the second registration overwrites the first; outbound publishes go through whichever one registered last (last-write-wins). symptom: outbound succeeds but through the wrong process. stop the manual one and reload. - **slack signing key missing.** the adapter fatals at start with `missing required env vars: slack_signing_secret`. under proxy_process this shows up as the service stuck in `degraded` state with the env-var name in the reason field. source the env file in the supervisor's launching shell. - **funnel rule out-of-band.** tailscale funnel `:443 → :8775` is not declared in the city. if you reboot the host or `tailscale funnel reset`, traffic stops landing at the adapter. re-add the rule manually until phase c lands. ## where the work that's still missing comes from the discord pack ships ~350k loc of python service code; the bulk of that is provider-agnostic state-machine logic for room peer fanout, launcher mode, slash-command intake, and workflow status projection. a meaningful chunk should eventually become a shared `extmsg-pack-lib` or similar so slack/discord/teams/etc. don't all reimplement it. for now we're porting feature-by-feature, against real usage from the oversight-rig pack. 0.0.2 refresh slack pack release pin. 0.0.3 ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark pack scripts/tests executable to align release hash with materialized modes.", + "search_text": "slack-full gascity-packs maintained gas city slack services, commands, and adapter integration for gas city. https://github.com/gastownhall/gascity-packs/tree/main/slack-full git # slack-full (v0.0.1 preview — scaffold) a slack provider extension for gas city. modeled directly on the upstream `discord` pack (https://github.com/gastownhall/gascity-packs/tree/main/discord) so the same primitives can be ported one at a time. this pack lives at `slack-full/` in the [`gastownhall/gascity-packs`](https://github.com/gastownhall/gascity-packs) catalog. a city opts in by importing it from that repository (see \"install\" below). ## tiering `slack-full` is **tier 3** of the slack pack family — workspace-grade orchestration. it is the original, full-surface pack; the two smaller tiers were later extracted from it for cities that need less. - **[slack-mini](../slack-mini)** (tier 1) — single-file adapter, one outbound verb (`gc slack-mini post-message`), `app_mention` → mayor. no channel bindings, no state. reach for it when you only need the mayor to speak into one channel. - **[slack-channel](../slack-channel)** (tier 2) — the \"team channel ↔ session graph\" middle tier: channel bindings, per-session identities, handle aliases, reactions, threaded replies. no multi-rig routing, slash commands, peer fanout, or launcher mode. - **slack-full** (tier 3, this pack) — workspace-grade orchestration: multi-rig routing, slash commands + interactions, peer fanout, launcher mode, bidirectional file attachments, and per-session identity overrides. ### choosing a tier - need the mayor to post status into a single channel and nothing else? → **slack-mini**. - need a handful of named sessions sharing one or more channels with distinct identities, but no slash commands or cross-rig routing? → **slack-channel**. - need slash commands, interactive modals/buttons, peer fanout across a room, launcher-mode session spawning, or multi-rig channel routing? → **slack-full** (this pack). see the [slack pack tiering design memo](../docs/design/slack-pack-tiering.md) for the full rationale. > **scope: not yet at parity with the discord pack.** the discord pack > ships ~350k loc of provider-agnostic python state-machine logic. > slack pack is a feature-by-feature port; today's surface is enough > to run a multi-session oversight loop end-to-end (dms + rooms + > peer fanout + identity overrides + bidirectional file > attachments), but several discord-pack features are still missing > (see \"not yet implemented\" below). if you need parity today, use > the discord pack. ## status implemented: - [x] `gc slack bind-dm` — bind a slack dm channel to one named session - [x] `gc slack bind-room` — bind a room to multiple sessions; requires exactly one of `--binding-owner session` / `--group-only` to declare the room's binding shape; further flags `--enable-peer-fanout`, `--allow-untargeted-publication`, `--max-peer-triggered-publishes`, `--max-total-peer-deliveries`, `--default-handle`, `--handle handle=session` (creates a launcher-mode group + participants under the hood) - [x] `gc slack reply-current` — reply to the latest slack event in the current session, by default through gc's `/extmsg/outbound` so transcript recording + peer fanout fire (`--via adapter` keeps the old direct-to-adapter path for diagnostics). a thread-reply inbound is answered in its thread by default (`--no-thread` forces a channel-level post) - [x] `gc slack publish` — publish to a session's saved binding (target session required, no event-scan fallback — fail-fast when the session has no active binding) - [x] `gc slack publish-to-channel` — publish to an arbitrary channel id (no session binding required; useful for one-shot ops posts) - [x] `gc slack status` — read-only diagnostics (adapters, bindings, recent traffic). `--session sid` for one-session detail, `--since 5m` for a time window (default `168h`; `--since ''` reads the full history), `--json` for scripting. exits 0 when every section was read, 2 when any section could not be; an unreadable section is marked `(unreadable: )` rather than being shown as empty, and the readable sections still print. - [x] `gc slack react` — add an emoji reaction to a slack message - [x] `gc slack identity` — register/unregister a per-session `chat:write.customize` identity (display name + icon) so each bound session posts under its own persona - [x] `gc slack handle-alias` — register/unregister a cross-channel `@handle` → session-id alias used by the address-by-handle protocol - [x] `gc slack upload` — bidirectional file attachments (`/publish-file` outbound + auto-download of inbound files into `$inbound_file_store//-`, scrubbed by an in-process retention janitor) - [x] `template-fragments/slack-v0.template.md` — composable prompt fragment for any agent in a slack-bound session - [x] pack-owned intake service (`[[service]]` proxy_process). phase a: adapter is the same go binary, but gc supervises it via uds for `/publish` while the public slack webhook still terminates at adapter tcp `:8775` (funnel unchanged). see \"adapter as a proxy_process service\" below for the cutover. slash-command / launcher / projection surface (operator cli verbs): - [x] `gc slack import-app` — register a slack app manifest with the gc city ([`manifest/readme.md`](./manifest/readme.md#importing-into-gc-gc-slack-import-app)) - [x] `gc slack map-channel` — bind a slack channel to a session; backs the adapter's `/slack/interactions` slash-command dispatcher. (note: the legacy `--rig` flag on this verb is deprecated as of gc-cby.25; use `gc slack map-rig` for rig→channel bindings. the flag still works for back-compat and emits a stderr deprecation warning.) - [x] `gc slack map-rig` — bind a rig to a set of channels as the fall-through default for slash-command resolution. per-channel `map-channel --session` bindings override rig defaults; channel mapping wins. cross-store conflict detection refuses contradictory writes in either direction. `--remove` drops the entire rig record; `--remove-channels c1,c2` drops just the listed channels (the record itself is deleted if the set becomes empty). both removal paths are idempotent. - [x] `gc slack sync-commands` — register/refresh the city's slash commands against the slack app (read by the adapter's `/slack/interactions` dispatcher). - [x] `gc slack sync-subteam-aliases` — reconcile `/.gc/slack/subteam-aliases.json` (the slack user group (\"subteam\") id → gc handle map) with the workspace's live user groups via slack `usergroups.list`. like `sync-commands`, it diffs live against on-disk and supports `--dry-run` and `--output text|json`; unlike it, the write is a non-destructive merge — existing entries are preserved, handles are refreshed where the live name differs, new user groups are added, and entries with no matching live group are kept and reported as \"local-only\" rather than dropped. the bot token (`--token` / `$slack_bot_token`) must carry the `usergroups:read` scope; without it the verb reports a readable `missing_scope` error and writes nothing. after a write it prints the same sighup reminder as the other registry-writing verbs. - [x] `gc slack post-message` — post (or `--update `) a block kit message to a channel via `chat.postmessage`/`chat.update`. the one-shot verb is shipped; automatic workflow-status projection from gc events is not yet wired. - [x] `gc slack retry-peer-fanout` (walks `extmsg.peer_fanout_failed` events, dedupes against successful `extmsg.peer_fanout_retried` events, re-issues each notification via the gc retry endpoint with a small cooldown between attempts) not yet implemented (planned): - [ ] `gc slack enable-room-launch` — the cli verb writes the `(workspace_id, channel_id) → pool_template` mapping today, but the adapter-side `@@` thread-scoped session spawn is still a stub: an `@@` post in an enabled channel currently gets an ephemeral \"launcher not yet available\" reply rather than spawning a session (tracked under the gc-cby epic). the adapter exposes `post /slack/interactions` (hmac-verified) for slash-command, `block_actions`, and `view_submission` dispatch. `block_actions` (button clicks, select-menu finalizations, datepickers, etc.) routes through the same channel-binding flow as slash commands: the action's originating channel (`payload.channel.id` falling back to `payload.container.channel_id`) must be bound via `gc slack map-channel` (session target) or `gc slack map-rig` (rig target — the rig branch is recorded but routing is deferred to gc-cby.18). `view_submission` (modal submits) carries no channel context, so the modal opener must set `view.private_metadata` to the json string `{\"session_id\":\"\"}` when calling `views.open`/`views.push`. on submit, the adapter strict-decodes that field (extra keys rejected, session_id length-capped) and posts a system-reminder to that session describing the `callback_id` plus `view.state.values`. any decode failure responds `{\"response_action\":\"clear\"}` so slack closes the modal stack and the user knows the submit did not process. the adapter's workspace gate (`slack_workspace_id` / `cfg.accountid`) applies on both branches — `payload.team.id` must match. `response_url` (valid ~30 minutes / 5 uses on `block_actions`) is forwarded to the agent in the system-reminder; persistent storage of `response_url` for later use is out of scope. other slack interaction types (`shortcut`, `message_action`, `view_closed`, `block_suggestion`) return an ephemeral \"unsupported interaction type\" reply — they need separate routing logic and are tracked under the gc-cby epic. ## binaries this pack ships **two** go binaries, each with its own `go.mod` so either can travel intact when the pack is mirrored upstream: - **adapter** — `slack-full/adapter/gc-slack-adapter`, built from `adapter/main.go`. the public-facing webhook receiver (slack → `:8775` over tailscale funnel) and outbound publisher (`/publish` over the controller-managed uds). long-running; gc supervises it via the `[[service]]` block in `pack.toml`. - **operator cli** — `slack-full/cli/gc-slack-cli`, built from `cli/main.go` + `cli/cmd/`. backs the `gc slack ` verb surface (import-app, map-channel, map-rig, sync-commands, sync-subteam-aliases, enable-room-launch, post-message). one-shot per invocation; not supervised. the pack's `commands/.sh` wrappers exec it at `$gc_pack_dir/cli/gc-slack-cli` so operator command-line ergonomics stay identical to the pre-relocation gc-binary verbs. build commands and ci for both binaries are documented in [contributing.md](./contributing.md#build-flow). ## architecture (current) the go adapter at `slack-full/adapter/gc-slack-adapter` (built from `adapter/main.go` colocated with this pack) is the public-facing webhook receiver and outbound publisher. this pack adds cli surface around it: `bind-dm` writes to gc's `/extmsg/bind` and to the pack's local config; `reply-current` reads recent gc events to find the conversation, then posts to gc's `/extmsg/outbound` (which calls the registered http adapter's `/publish` endpoint internally and emits `extmsgoutbound` so peer fanout fires for bind-room sessions). `--via adapter` is available for adapter-only diagnostics that bypass gc. ``` ┌──── public ────┐ slack ──hmac──▶ go adapter :8775 ──▶ gc /extmsg/inbound go adapter :8766 ◀── gc /extmsg/outbound ◀── gc slack reply-current ◀── (--via adapter) ─────── gc slack reply-current └────────────────┘ ``` the `:8775` public port above is the reference funnel-fronted deployment (`listen_public=:8775`, with a tailscale funnel rule `:443 → :8775`). the bare adapter default is `:8765` — see the `listen_public` row in \"adapter env contract\" below. the adapter's `/publish` endpoint **requires session attribution**: every request must carry a `session_id` (or, for older gc binaries, the legacy `metadata.source_session_id`). a request with neither is rejected with http 400 and nothing is posted to slack: ```json {\"error\": \"publish requires session attribution: provide session_id or metadata.source_session_id\"} ``` this fails closed against identity-less channel-root posts under the default bot identity. system/bot status notifications that intentionally post as the bot go through `gc slack post-message` (direct to slack `chat.postmessage`), which bypasses `/publish` entirely and is unaffected by this guard. ## install import the pack into your city: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//slack-full ``` that writes the import and pins the release in `packs.lock`. the same import by hand in `pack.toml`, followed by `gc import install`: ```toml [imports.slack-full] source = \"https://github.com/gastownhall/gascity-packs.git//slack-full\" ``` `gc pack registry show slack-full` prints the registry entry and its releases. to work on the pack from a local checkout, point `source` at the path instead: ```toml [imports.slack-full] source = \"/path/to/gascity-packs/slack-full\" ``` then `gc reload` (or wait for the supervisor to pick up the change). verify the commands appear: ``` gc slack --help gc slack bind-dm --help gc slack reply-current --help ``` ## verify assuming the adapter is up and the four must-set vars (`slack_workspace_id`, `slack_bot_token`, `slack_signing_secret`, `gc_city_name`) are exported (sourced from `${xdg_config_home:-$home/.config}/gc-slack-adapter/env` — see \"adapter env contract\" below): ``` # bind a dm channel to a session. replace d0xxxxxxxxx with the dm # channel id slack assigned to your bot's im with the human user. gc slack bind-dm d0xxxxxxxxx my-session.cos # from inside that session (or any session that has seen recent # extmsg.inbound on a slack conversation): echo \"*my-session.cos:* ack via slack pack\" > /tmp/reply.txt gc slack reply-current --body-file /tmp/reply.txt ``` the reply should land in your slack dm. to bind a room (public or private channel) to multiple sessions so that two or more agents are visible peers and a human can join the conversation: ``` gc slack bind-room c0xxxxxxxxx \\ my-pack.session-a my-pack.session-b \\ --enable-peer-fanout \\ --binding-owner my-pack.session-b ``` both sessions then receive an inbound system reminder for every human message in the channel; `extmsg.inbound` events list both as conversation members. when the publishing session calls `gc slack reply-current` (default `--via gc`), gc records the publish in the conversation transcript and fans out a peer-publication reminder to the other bound sessions so they see what their peer just said. `--binding-owner session` is what makes outbound publishes (and therefore `gc slack reply-current --via gc`) actually work: without a direct binding, `/extmsg/outbound` has no `sessionbindingrecord` to resolve the conversation through and the publish is rejected, even though peer fanout still fires on inbound. the owner must be one of the participants — prefer the session that \"owns\" the room from gc's perspective. pass the gc session id (e.g. `gc-77139`) when alias resolution semantics matter; for stable named sessions, the alias works too. every run must declare which of the two shapes it wants: pass `--binding-owner session` for a room with an outbound publisher, or `--group-only` for a purely group-routed room. `--group-only` removes *every* active direct binding for the conversation — including ones created by other tooling — so that none can shadow the group route, and it prints each removed binding to stderr. because that removal is destructive, it is never the default: a run with neither flag is rejected before any api call rather than quietly sweeping the room's publisher. re-running `bind-room` on an owned room therefore has to name the owner again. ### threaded replies when a human replies inside a slack thread, the text the session receives starts with a `[slack thread_ts=]` line naming the thread root. after it comes the earlier thread context: the root message's text on every reply, even when a bot posted it (shown as `[bot ]: ...`), plus any human messages newer than the last reply this session already received. other bot messages, and messages with no text (a file-only root, for example), are left out. if the adapter cannot fetch the thread from slack, the marker line is followed by `thread parent could not be fetched.` instead of the context. `gc slack reply-current` already answers in the thread; with `gc slack publish-to-channel`, pass the root ts as `--thread-ts`. messages outside a thread arrive without the marker. ## adapter as a proxy_process service phase a of the in-pack adapter (tracked as bd `gc-5rz`) lets gc supervise the adapter as part of the city's services. the adapter binds a unix domain socket for the `/publish` endpoint that gc reaches via the extmsg http adapter, and gc reverse-proxies `/svc/slack/*` to that uds. the public slack webhook (`/slack/events`) still terminates at the adapter's tcp `:8775` so tailscale funnel and slack's signing secret verification are unchanged. the same binary runs in both modes — the legacy `nohup ./run.sh` deployment is preserved as a rollback target. ### what gc injects vs. what stays in the env file `proxy_process` injects the controller-managed env at start time: - `gc_service_name=slack` - `gc_service_socket=/tmp/gcsvc-//slack-*.sock` - `gc_service_url_prefix=/svc/slack` - `gc_service_state_root=.../.gc/services/slack` - `gc_service_run_root=.../.gc/services/slack/run` note: `gc_api_base_url` and `gc_city_name` are not injected by the controller for `proxy_process` services — they must be present in the env file you source before `gc start`, so the supervisor inherits them and passes them down to the spawned adapter. when `gc_service_socket` is set, the adapter: - skips its `listen_internal` tcp listener and binds the uds instead; - computes its self-registration `callbackurl` as `$gc_api_base_url + $gc_service_url_prefix` (gc's extmsg http adapter appends `/publish` itself when calling out, so the registered base url must not include `/publish`); - still binds public tcp for `/slack/events`; - still serves `/healthz` on the uds so the controller's `health_path` probe succeeds. ### adapter env contract the full adapter env contract — what the binary at `slack-full/adapter/main.go` reads — is enumerated in the package docstring at the top of that file. summary: **must-set** (no default; adapter exits at startup if missing): | var | purpose | | ---------------------- | ------------------------------------------------------------------ | | `slack_workspace_id` | slack team id (e.g. `t0xxxxxxxxx`). | | `slack_bot_token` | `xoxb-…` token. scopes: `chat:write`, `reactions:write`, `files:write`, and `chat:write.customize` for per-session identity overrides. | | `slack_signing_secret` | used to verify hmac signatures on `/slack/events` requests. | | `gc_city_name` | gc city the adapter posts inbound + session-message traffic to. matches `[workspace].name` in `city.toml`. no fallback default — the adapter fails fast rather than silently route to a wrong city. | **optional override** (sane default; set to override): | var | default | purpose | | ------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------- | | `listen_public` | `:8765` | public listener for `/slack/events` (bind `0.0.0.0` if fronted by a tunnel). | | `listen_internal` | `127.0.0.1:8766` | loopback listener for `/publish`. ignored under proxy_process mode. | | `internal_callback_url` | `http://127.0.0.1:8766` | url advertised to gc at self-registration. ignored under proxy_process mode. | | `gc_api_base_url` | `http://127.0.0.1:9443` | base url for gc's http api. | | `adapter_provider` | `slack` | provider name in conversation refs + adapter registration. | | `register_on_start` | `true` | set `false` to skip `/extmsg/adapters` registration (tests, diagnostics). | | `handle_prefix` | `@` | leading address token for keyword routing. empty disables routing. | | `identity_store_path` | `/tmp/gc-slack-adapter/identities.json` | json file backing the per-session `chat:write.customize` identity registry. | | `handle_alias_store_path` | `/tmp/gc-slack-adapter/handle-aliases.json` | json file backing the cross-channel handle → session-id alias registry. | | `slack_subteam_alias_file` | `/.gc/slack/subteam-aliases.json` (or `/tmp/gc-slack-adapter/subteam-aliases.json`) | json map of slack user group (\"subteam\") ids → gc handles, hand-edited or written by `gc slack sync-subteam-aliases`. required to route the unlabeled `` mention shape; the labeled `` / `` shape is gated by `handle_alias_store_path` instead. read-only at runtime; sighup or restart to reload. | | `slack_user_alias_file` | `/.gc/slack/slack-user-aliases.json` (or `/tmp/gc-slack-adapter/slack-user-aliases.json`) | json map of bare gc handle → raw slack target id — a user (`uxxxx`/`wxxxx`) or user group (`sxxxx`). the **outbound** inverse of `slack_subteam_alias_file`: `/publish` rewrites `@handle` body tokens to slack mention syntax (`<@uxxxx>` / ``) so they render as clickable, notifying mentions instead of literal text. only handles present in the map are rewritten — unmapped handles stay literal (fail-safe, no surprise pings). the `@` is only treated as a mention at a left word boundary, so email-like `user@host` strings are left intact. hand-edited (operator-curated allowlist); read-only at runtime, sighup or restart to reload. | | `inbound_file_store` | `/tmp/gc-slack-adapter/inbound` | directory for downloaded inbound slack file attachments. | | `inbound_file_ttl` | `168h` (7 days) | janitor retention. `0` disables sweeping. | | `inbound_file_sweep_interval` | `1h` | janitor scan period. `0` disables sweeping. | | `slack_dispatch_concurrency` | `50` | cap on in-flight inbound-dispatch goroutines (slash-command, slack-event, alias-resolved). on saturation the adapter drops the dispatch with a `dispatch queue full` log line; the inbound post itself is not affected. must be a positive integer; 0/negative/non-numeric values fail at startup. (sec-s-04) | **permissions:** `identity_store_path`, `handle_alias_store_path`, and `inbound_file_store` are written with `0o700` directories and `0o600` files so contents are readable only by the adapter's uid. on startup the adapter additionally tightens any pre-existing files/directories that are looser (one-shot migration for legacy `/tmp/gc-slack-adapter/` trees from earlier versions). operators using a custom-mode parent (e.g. setgid for shared-group access) should set the perms before adapter start; the tightener preserves setuid/setgid/sticky bits and never loosens an operator-tighter mode. for multi-tenant hosts, override these paths to a host-private location explicitly created with `0o700`. the proxy_process unix domain socket (`gc_service_socket`) is also chmod'd to `0o600` after bind as defense-in-depth on top of its `0o700` controller-managed parent directory at `/tmp/gcsvc-//`. **controller-injected** (proxy_process mode only — gc sets these when the adapter runs as a `[[service]]`; do not set them by hand): `gc_service_name`, `gc_service_socket`, `gc_service_url_prefix`, `gc_service_state_root`, `gc_service_run_root`. see \"what gc injects vs. what stays in the env file\" above. ### sighup-driven reload five of the adapter's registries are written by `gc slack` cli commands and read by the adapter at startup: | registry | file | cli writer | | -------------------- | ----------------------------- | ------------------------------- | | apps | `apps.json` | `gc slack import-app` | | channel mappings | `channel_mappings.json` | `gc slack map-channel` | | rig mappings | `rig_mappings.json` | `gc slack map-rig` | | room launch mappings | `room_launch_mappings.json` | `gc slack enable-room-launch` | | subteam aliases | `subteam-aliases.json` | `gc slack sync-subteam-aliases` | send `sighup` to the running adapter to pick up cli-driven changes without a full restart: ``` pkill -hup gc-slack-adapter ``` reload is all-or-nothing across the five files — a single parse failure (corrupt json, unknown `target_kind`, missing required field, file >10 mib) aborts the cycle with the live in-memory state untouched. errors are logged at warn; the adapter keeps serving. a missing file is a **no-op** (live state is preserved). to clear a registry, write an empty json object instead of removing the file: ``` echo '{}' > /.gc/slack/channel_mappings.json pkill -hup gc-slack-adapter ``` the other three registries — `identity_store_path`, `handle_alias_store_path`, and the thread-session store — are **adapter-owned**: written in-process by the adapter itself (via `/identity`, `/handle-alias`, and the launcher), not by the cli, so they do not participate in sighup reload. do not hand-edit them while the adapter is running — it rewrites them from its in-memory state at will, so manual edits are silently overwritten. the five files in the table above are the operator-editable surface; everything else under the store paths belongs to the adapter. `apps.json` is the one file written from **both** sides (the cli's `import-app` and the adapter's oauth callback). the adapter generation-stamps its reload snapshots so a sighup racing an oauth callback re-stages instead of rolling the in-memory view back to the pre-callback state; if the log shows `apps registry commit skipped`, re-send the sighup. **consumer-specific** (referenced by deployment scripts and prompts in sibling tooling, not by the adapter binary): variables consumed by `deliver-rollup.sh`, `resolve_rig_channel.py`, etc., live with the consumer pack and are documented there. the adapter has no opinion on what handles map to which sessions — that's `/handle-alias` registry content, set by the consumer at deploy time. ### slack secrets — placement recommended placement for the four must-set vars (and any overrides): `${xdg_config_home:-$home/.config}/gc-slack-adapter/env` (`0600` perms). source the file before `gc start` so the adapter inherits the env via `os.environ()`. under systemd-managed deployments, drop a unit override that points `environmentfile=-…/gc-slack-adapter/env` at the same path so the supervisor passes the env to its `proxy_process` children. phase b will move the env-file path into pack config; not yet wired. ``` slack_workspace_id=t0xxxxxxxxx slack_bot_token=xoxb-... slack_signing_secret=... gc_city_name= ``` ### cutover sequence ``` # 1. build the adapter binary in place (source colocated with the pack). # optional: the [[service]] command is adapter/run.sh, which rebuilds # gc-slack-adapter itself when the binary is missing (e.g. after # `gc import install` re-materialized the pack cache git-only). ( cd slack-full/adapter && go build -o gc-slack-adapter ) # 2. source the secrets so the supervisor inherits them. # optional: run.sh sources this same file itself at service start # (override the path with gc_slack_adapter_env — if you set it, the # file must exist: run.sh refuses to start on ambient credentials # rather than post to whatever workspace is in the environment). set -a; source \"${xdg_config_home:-$home/.config}/gc-slack-adapter/env\"; set +a # 3. stop any manually-managed adapter that may still be running pkill -f gc-slack-adapter || true # 4. reload the city so the [[service]] block from slack-full registers gc reload # or: gc supervisor reload # 5. verify the service is ready gc service list # expect: slack proxy_process ready curl --unix-socket \"$(gc service show slack --json | jq -r .socket)\" \\ http://x/healthz # expect: 200, first line \"ok\", # then dispatch_dropped_total=n # (inbound events dropped because # the dispatch pool was saturated; # a growing n means raise # slack_dispatch_concurrency) # 6. verify outbound publish through gc (replace the placeholders). # gc_api_base_url defaults to http://127.0.0.1:8372 on a single-host # deployment; gc_city_name is the directory-name of your city. curl -s -x post -h \"content-type: application/json\" -h \"x-gc-request: cutover\" \\ -d '{\"session_id\":\"\",\"conversation\":{\"scope_id\":\"\",\"provider\":\"slack\",\"account_id\":\"t0xxxxxxxxx\",\"conversation_id\":\"d0xxxxxxxxx\",\"kind\":\"dm\"},\"text\":\"*cutover:* hello\"}' \\ \"${gc_api_base_url:-http://127.0.0.1:8372}/v0/city/${gc_city_name:-}/extmsg/outbound\" \\ | jq '.receipt.delivered' # expect: true # 7. verify inbound — send a slack dm to the bot, then: gc events --city \"${gc_city_name:-}\" --type extmsg.inbound --since 2m ``` rollback: remove (or comment out) the `[[service]]` block in `pack.toml`, `gc reload`, then restart the manual adapter via the legacy script: ``` ( cd slack-full/adapter \\ && nohup ./run.sh > /tmp/gc-slack-adapter/run.log 2>&1 & disown ) ``` the adapter ignores `$gc_service_socket` when unset and falls back to tcp-only mode, so the same binary serves both deployments. ### known foot-guns - **two adapters running.** if you forget step 3 and the manual adapter stays up while gc starts the proxy_process one, both will call `/extmsg/adapters` to register. the second registration overwrites the first; outbound publishes go through whichever one registered last (last-write-wins). symptom: outbound succeeds but through the wrong process. stop the manual one and reload. - **slack signing key missing.** the adapter fatals at start with `missing required env vars: slack_signing_secret`. under proxy_process this shows up as the service stuck in `degraded` state with the env-var name in the reason field. source the env file in the supervisor's launching shell. - **funnel rule out-of-band.** tailscale funnel `:443 → :8775` is not declared in the city. if you reboot the host or `tailscale funnel reset`, traffic stops landing at the adapter. re-add the rule manually until phase c lands. ## where the work that's still missing comes from the discord pack ships ~350k loc of python service code; the bulk of that is provider-agnostic state-machine logic for room peer fanout, launcher mode, slash-command intake, and workflow status projection. a meaningful chunk should eventually become a shared `extmsg-pack-lib` or similar so slack/discord/teams/etc. don't all reimplement it. for now we're porting feature-by-feature, against real usage from the oversight-rig pack. 0.0.2 refresh slack pack release pin. 0.0.3 ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark pack scripts/tests executable to align release hash with materialized modes. 0.0.4 rolls up main since 0.0.3: company rooms, per-agent and group dms, delivery that wakes sleeping sessions, a run.sh entrypoint that rebuilds a missing adapter, thread-root context on every threaded reply, app manifests that slack accepts on import, and gc slack status reporting unreadable sections instead of empty ones. 0.0.5 the adapter start script builds in private per-user directories when home is unset, instead of shared fixed paths under tmpdir that another local user could create first.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/slack-full/README.md", - "content": "# slack-full (v0.0.1 preview — scaffold)\n\nA Slack provider extension for Gas City. Modeled directly on the\nupstream `discord` pack\n(https://github.com/gastownhall/gascity-packs/tree/main/discord) so\nthe same primitives can be ported one at a time.\n\nThis pack lives at `slack-full/` in the\n[`gastownhall/gascity-packs`](https://github.com/gastownhall/gascity-packs)\ncatalog. A city opts in by path-importing its `pack.toml` (see\n\"Install\" below).\n\n## Tiering\n\n`slack-full` is **Tier 3** of the Slack pack family — workspace-grade\norchestration. It is the original, full-surface pack; the two smaller\ntiers were later extracted from it for cities that need less.\n\n- **[slack-mini](../slack-mini)** (Tier 1) — single-file adapter, one\n outbound verb (`gc slack-mini post-message`), `app_mention` → mayor.\n No channel bindings, no state. Reach for it when you only need the\n mayor to speak into one channel.\n- **[slack-channel](../slack-channel)** (Tier 2) — the \"team channel ↔\n session graph\" middle tier: channel bindings, per-session identities,\n handle aliases, reactions, threaded replies. No multi-rig routing,\n slash commands, peer fanout, or launcher mode.\n- **slack-full** (Tier 3, this pack) — workspace-grade orchestration:\n multi-rig routing, slash commands + interactions, peer fanout,\n launcher mode, bidirectional file attachments, and per-session\n identity overrides.\n\n### Choosing a tier\n\n- Need the mayor to post status into a single channel and nothing\n else? → **slack-mini**.\n- Need a handful of named sessions sharing one or more channels with\n distinct identities, but no slash commands or cross-rig routing? →\n **slack-channel**.\n- Need slash commands, interactive modals/buttons, peer fanout across a\n room, launcher-mode session spawning, or multi-rig channel routing? →\n **slack-full** (this pack).\n\nSee the [Slack pack tiering design memo](../docs/design/slack-pack-tiering.md)\nfor the full rationale.\n\n> **Scope: not yet at parity with the discord pack.** The discord pack\n> ships ~350K LOC of provider-agnostic Python state-machine logic.\n> Slack pack is a feature-by-feature port; today's surface is enough\n> to run a multi-session oversight loop end-to-end (DMs + rooms +\n> peer fanout + identity overrides + bidirectional file\n> attachments), but several discord-pack features are still missing\n> (see \"Not yet implemented\" below). If you need parity today, use\n> the discord pack.\n\n## Status\n\nImplemented:\n\n- [x] `gc slack bind-dm` — bind a Slack DM channel to one named session\n- [x] `gc slack bind-room` — bind a room to multiple sessions; flags\n `--enable-peer-fanout`, `--allow-untargeted-publication`,\n `--max-peer-triggered-publishes`, `--max-total-peer-deliveries`,\n `--default-handle`, `--handle HANDLE=SESSION` (creates a\n launcher-mode group + participants under the hood)\n- [x] `gc slack reply-current` — reply to the latest Slack event in the\n current session, by default through gc's `/extmsg/outbound` so\n transcript recording + peer fanout fire (`--via adapter` keeps the\n old direct-to-adapter path for diagnostics)\n- [x] `gc slack publish` — publish to a session's saved binding (target\n session required, no event-scan fallback — fail-fast when the\n session has no active binding)\n- [x] `gc slack publish-to-channel` — publish to an arbitrary channel\n ID (no session binding required; useful for one-shot ops posts)\n- [x] `gc slack status` — read-only diagnostics (adapters, bindings,\n recent traffic). `--session SID` for one-session detail,\n `--since 5m` for a time window, `--json` for scripting.\n- [x] `gc slack react` — add an emoji reaction to a Slack message\n- [x] `gc slack identity` — register/unregister a per-session\n `chat:write.customize` identity (display name + icon) so each\n bound session posts under its own persona\n- [x] `gc slack handle-alias` — register/unregister a cross-channel\n `@handle` → session-id alias used by the address-by-handle\n protocol\n- [x] `gc slack upload` — bidirectional file attachments\n (`/publish-file` outbound + auto-download of inbound files into\n `$INBOUND_FILE_STORE//-`, scrubbed by an\n in-process retention janitor)\n- [x] `template-fragments/slack-v0.template.md` — composable prompt\n fragment for any agent in a slack-bound session\n- [x] Pack-owned intake service (`[[service]]` proxy_process). Phase A:\n adapter is the same Go binary, but gc supervises it via UDS for\n `/publish` while the public Slack webhook still terminates at\n adapter TCP `:8775` (Funnel unchanged). See \"Adapter as a\n proxy_process service\" below for the cutover.\n\nSlash-command / launcher / projection surface (operator CLI verbs):\n\n- [x] `gc slack import-app` — register a Slack app manifest with the gc\n city ([`manifest/README.md`](./manifest/README.md#importing-into-gc-gc-slack-import-app))\n- [x] `gc slack map-channel` — bind a Slack channel to a session;\n backs the adapter's `/slack/interactions` slash-command\n dispatcher. (Note: the legacy `--rig` flag on this verb is\n deprecated as of gc-cby.25; use `gc slack map-rig` for\n rig→channel bindings. The flag still works for back-compat\n and emits a stderr deprecation warning.)\n- [x] `gc slack map-rig` — bind a rig to a set of channels as the\n fall-through default for slash-command resolution. Per-channel\n `map-channel --session` bindings override rig defaults; channel\n mapping wins. Cross-store conflict detection refuses contradictory\n writes in either direction. `--remove` drops the entire rig\n record; `--remove-channels c1,c2` drops just the listed channels\n (the record itself is deleted if the set becomes empty). Both\n removal paths are idempotent.\n- [x] `gc slack sync-commands` — register/refresh the city's slash\n commands against the Slack app (read by the adapter's\n `/slack/interactions` dispatcher).\n- [x] `gc slack sync-subteam-aliases` — reconcile\n `/.gc/slack/subteam-aliases.json` (the Slack User Group\n (\"subteam\") id → gc handle map) with the workspace's live User\n Groups via Slack `usergroups.list`. Like `sync-commands`, it diffs\n live against on-disk and supports `--dry-run` and\n `--output text|json`; unlike it, the write is a non-destructive\n MERGE — existing entries are preserved, handles are refreshed where\n the live name differs, new User Groups are added, and entries with\n no matching live group are kept and reported as \"local-only\" rather\n than dropped. The bot token (`--token` / `$SLACK_BOT_TOKEN`) must\n carry the `usergroups:read` scope; without it the verb reports a\n readable `missing_scope` error and writes nothing. After a write it\n prints the same SIGHUP reminder as the other registry-writing verbs.\n- [x] `gc slack post-message` — post (or `--update `) a Block Kit\n message to a channel via `chat.postMessage`/`chat.update`. The\n one-shot verb is shipped; automatic workflow-status projection\n from gc events is not yet wired.\n- [x] `gc slack retry-peer-fanout` (walks `extmsg.peer_fanout_failed`\n events, dedupes against successful `extmsg.peer_fanout_retried`\n events, re-issues each notification via the gc retry endpoint with\n a small cooldown between attempts)\n\nNot yet implemented (planned):\n\n- [ ] `gc slack enable-room-launch` — the CLI verb writes the\n `(workspace_id, channel_id) → pool_template` mapping today, but the\n adapter-side `@@` thread-scoped session spawn is still a\n stub: an `@@` post in an enabled channel currently gets an\n ephemeral \"launcher not yet available\" reply rather than spawning a\n session (tracked under the gc-cby epic).\n\nThe adapter exposes `POST /slack/interactions` (HMAC-verified) for\nslash-command, `block_actions`, and `view_submission` dispatch.\n\n`block_actions` (button clicks, select-menu finalizations, datepickers,\netc.) routes through the same channel-binding flow as slash commands:\nthe action's originating channel (`payload.channel.id` falling back to\n`payload.container.channel_id`) must be bound via `gc slack\nmap-channel` (session target) or `gc slack map-rig` (rig target — the\nrig branch is recorded but routing is deferred to gc-cby.18).\n\n`view_submission` (modal submits) carries no channel context, so the\nmodal opener MUST set `view.private_metadata` to the JSON string\n`{\"session_id\":\"\"}` when calling `views.open`/`views.push`.\nOn submit, the adapter strict-decodes that field (extra keys rejected,\nsession_id length-capped) and posts a system-reminder to that session\ndescribing the `callback_id` plus `view.state.values`. Any decode\nfailure responds `{\"response_action\":\"clear\"}` so Slack closes the\nmodal stack and the user knows the submit did not process.\n\nThe adapter's workspace gate (`SLACK_WORKSPACE_ID` / `cfg.accountID`)\napplies on both branches — `payload.team.id` must match. `response_url`\n(valid ~30 minutes / 5 uses on `block_actions`) is forwarded to the\nagent in the system-reminder; persistent storage of `response_url` for\nlater use is out of scope.\n\nOther Slack interaction types (`shortcut`, `message_action`,\n`view_closed`, `block_suggestion`) return an ephemeral \"unsupported\ninteraction type\" reply — they need separate routing logic and are\ntracked under the gc-cby epic.\n\n## Binaries\n\nThis pack ships **two** Go binaries, each with its own `go.mod` so\neither can travel intact when the pack is mirrored upstream:\n\n- **Adapter** — `slack-full/adapter/gc-slack-adapter`,\n built from `adapter/main.go`. The public-facing webhook receiver\n (Slack → `:8775` over Tailscale Funnel) and outbound publisher\n (`/publish` over the controller-managed UDS). Long-running; gc\n supervises it via the `[[service]]` block in `pack.toml`.\n\n- **Operator CLI** — `slack-full/cli/gc-slack-cli`, built\n from `cli/main.go` + `cli/cmd/`. Backs the `gc slack ` verb\n surface (import-app, map-channel, map-rig, sync-commands,\n sync-subteam-aliases, enable-room-launch, post-message). One-shot per invocation; not\n supervised. The pack's `commands/.sh` wrappers exec it at\n `$GC_PACK_DIR/cli/gc-slack-cli` so operator command-line ergonomics\n stay identical to the pre-relocation gc-binary verbs.\n\nBuild commands and CI for both binaries are documented in\n[CONTRIBUTING.md](./CONTRIBUTING.md#build-flow).\n\n## Architecture (current)\n\nThe Go adapter at `slack-full/adapter/gc-slack-adapter`\n(built from `adapter/main.go` colocated with this pack) is the\npublic-facing webhook receiver and outbound publisher. This\npack adds CLI surface around it: `bind-dm` writes to gc's\n`/extmsg/bind` and to the pack's local config; `reply-current` reads\nrecent gc events to find the conversation, then POSTs to gc's\n`/extmsg/outbound` (which calls the registered HTTP adapter's\n`/publish` endpoint internally and emits `ExtMsgOutbound` so peer\nfanout fires for bind-room sessions). `--via adapter` is available\nfor adapter-only diagnostics that bypass gc.\n\n```\n ┌──── public ────┐\nSlack ──HMAC──▶ Go adapter :8775 ──▶ gc /extmsg/inbound\n Go adapter :8766 ◀── gc /extmsg/outbound ◀── gc slack reply-current\n ◀── (--via adapter) ─────── gc slack reply-current\n └────────────────┘\n```\n\nThe `:8775` public port above is the reference Funnel-fronted\ndeployment (`LISTEN_PUBLIC=:8775`, with a Tailscale Funnel rule\n`:443 → :8775`). The bare adapter default is `:8765` — see the\n`LISTEN_PUBLIC` row in \"Adapter env contract\" below.\n\nThe adapter's `/publish` endpoint **requires session attribution**: every\nrequest must carry a `session_id` (or, for older gc binaries, the legacy\n`metadata.source_session_id`). A request with neither is rejected with HTTP\n400 and nothing is posted to Slack:\n\n```json\n{\"error\": \"publish requires session attribution: provide session_id or metadata.source_session_id\"}\n```\n\nThis fails closed against identity-less channel-root posts under the default\nbot identity. System/bot status notifications that intentionally post as the bot\ngo through `gc slack post-message` (direct to Slack `chat.postMessage`),\nwhich bypasses `/publish` entirely and is unaffected by this guard.\n\n## Install\n\n```toml\n# city.toml\n[imports.slack-full]\nsource = \"/path/to/gascity-packs/slack-full\"\n```\n\nThen `gc reload` (or wait for the supervisor to pick up the change).\nVerify the commands appear:\n\n```\ngc slack --help\ngc slack bind-dm --help\ngc slack reply-current --help\n```\n\n## Verify\n\nAssuming the adapter is up and the four must-set vars\n(`SLACK_WORKSPACE_ID`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`,\n`GC_CITY_NAME`) are exported (sourced from\n`${XDG_CONFIG_HOME:-$HOME/.config}/gc-slack-adapter/env` — see\n\"Adapter env contract\" below):\n\n```\n# Bind a DM channel to a session. Replace D0XXXXXXXXX with the DM\n# channel ID Slack assigned to your bot's IM with the human user.\ngc slack bind-dm D0XXXXXXXXX my-session.cos\n\n# From inside that session (or any session that has seen recent\n# extmsg.inbound on a slack conversation):\necho \"*my-session.cos:* ack via slack pack\" > /tmp/reply.txt\ngc slack reply-current --body-file /tmp/reply.txt\n```\n\nThe reply should land in your Slack DM.\n\nTo bind a room (public or private channel) to multiple sessions so\nthat two or more agents are visible peers and a human can join the\nconversation:\n\n```\ngc slack bind-room C0XXXXXXXXX \\\n my-pack.session-a my-pack.session-b \\\n --enable-peer-fanout \\\n --binding-owner my-pack.session-b\n```\n\nBoth sessions then receive an inbound system reminder for every human\nmessage in the channel; `extmsg.inbound` events list both as\nconversation members. When the publishing session calls\n`gc slack reply-current` (default `--via gc`), gc records the publish\nin the conversation transcript and fans out a peer-publication\nreminder to the other bound sessions so they see what their peer just\nsaid.\n\n`--binding-owner SESSION` is what makes outbound publishes (and\ntherefore `gc slack reply-current --via gc`) actually work. Without\nit, peer fanout still fires on inbound, but `/extmsg/outbound` has\nno `SessionBindingRecord` to resolve the conversation through and the\npublish is rejected. The owner must be one of the participants —\nprefer the session that \"owns\" the room from gc's perspective. Pass\nthe gc session id (e.g. `gc-77139`) when alias resolution semantics\nmatter; for stable named sessions, the alias works too.\n\n## Adapter as a proxy_process service\n\nPhase A of the in-pack adapter (tracked as bd `gc-5rz`) lets gc\nsupervise the adapter as part of the city's services. The adapter\nbinds a Unix domain socket for the `/publish` endpoint that gc reaches\nvia the extmsg HTTP adapter, and gc reverse-proxies `/svc/slack/*` to\nthat UDS. The public Slack webhook (`/slack/events`) still terminates\nat the adapter's TCP `:8775` so Tailscale Funnel and Slack's signing\nsecret verification are unchanged. The same binary runs in both modes\n— the legacy `nohup ./run.sh` deployment is preserved as a rollback\ntarget.\n\n### What gc injects vs. what stays in the env file\n\n`proxy_process` injects the controller-managed env at start time:\n\n- `GC_SERVICE_NAME=slack`\n- `GC_SERVICE_SOCKET=/tmp/gcsvc-//slack-*.sock`\n- `GC_SERVICE_URL_PREFIX=/svc/slack`\n- `GC_SERVICE_STATE_ROOT=.../.gc/services/slack`\n- `GC_SERVICE_RUN_ROOT=.../.gc/services/slack/run`\n\nNote: `GC_API_BASE_URL` and `GC_CITY_NAME` are NOT injected by the\ncontroller for `proxy_process` services — they must be present in the\nenv file you source before `gc start`, so the supervisor inherits them\nand passes them down to the spawned adapter.\n\nWhen `GC_SERVICE_SOCKET` is set, the adapter:\n- skips its `LISTEN_INTERNAL` TCP listener and binds the UDS instead;\n- computes its self-registration `CallbackURL` as\n `$GC_API_BASE_URL + $GC_SERVICE_URL_PREFIX` (gc's extmsg HTTP adapter\n appends `/publish` itself when calling out, so the registered base\n URL must NOT include `/publish`);\n- still binds public TCP for `/slack/events`;\n- still serves `/healthz` on the UDS so the controller's `health_path`\n probe succeeds.\n\n### Adapter env contract\n\nThe full adapter env contract — what the binary at\n`slack-full/adapter/main.go` reads — is enumerated in the\npackage docstring at the top of that file. Summary:\n\n**Must-set** (no default; adapter exits at startup if missing):\n\n| Var | Purpose |\n| ---------------------- | ------------------------------------------------------------------ |\n| `SLACK_WORKSPACE_ID` | Slack team id (e.g. `T0XXXXXXXXX`). |\n| `SLACK_BOT_TOKEN` | `xoxb-…` token. Scopes: `chat:write`, `reactions:write`, `files:write`, and `chat:write.customize` for per-session identity overrides. |\n| `SLACK_SIGNING_SECRET` | Used to verify HMAC signatures on `/slack/events` requests. |\n| `GC_CITY_NAME` | gc city the adapter posts inbound + session-message traffic to. Matches `[workspace].name` in `city.toml`. No fallback default — the adapter fails fast rather than silently route to a wrong city. |\n\n**Optional override** (sane default; set to override):\n\n| Var | Default | Purpose |\n| ------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------- |\n| `LISTEN_PUBLIC` | `:8765` | Public listener for `/slack/events` (bind `0.0.0.0` if fronted by a tunnel). |\n| `LISTEN_INTERNAL` | `127.0.0.1:8766` | Loopback listener for `/publish`. Ignored under proxy_process mode. |\n| `INTERNAL_CALLBACK_URL` | `http://127.0.0.1:8766` | URL advertised to gc at self-registration. Ignored under proxy_process mode. |\n| `GC_API_BASE_URL` | `http://127.0.0.1:9443` | Base URL for gc's HTTP API. |\n| `ADAPTER_PROVIDER` | `slack` | Provider name in conversation refs + adapter registration. |\n| `REGISTER_ON_START` | `true` | Set `false` to skip `/extmsg/adapters` registration (tests, diagnostics). |\n| `HANDLE_PREFIX` | `@` | Leading address token for keyword routing. Empty disables routing. |\n| `IDENTITY_STORE_PATH` | `/tmp/gc-slack-adapter/identities.json` | JSON file backing the per-session `chat:write.customize` identity registry. |\n| `HANDLE_ALIAS_STORE_PATH` | `/tmp/gc-slack-adapter/handle-aliases.json` | JSON file backing the cross-channel handle → session-id alias registry. |\n| `SLACK_SUBTEAM_ALIAS_FILE` | `/.gc/slack/subteam-aliases.json` (or `/tmp/gc-slack-adapter/subteam-aliases.json`) | JSON map of Slack User Group (\"subteam\") IDs → gc handles, hand-edited or written by `gc slack sync-subteam-aliases`. Required to route the unlabeled `` mention shape; the labeled `` / `` shape is gated by `HANDLE_ALIAS_STORE_PATH` instead. Read-only at runtime; SIGHUP or restart to reload. |\n| `SLACK_USER_ALIAS_FILE` | `/.gc/slack/slack-user-aliases.json` (or `/tmp/gc-slack-adapter/slack-user-aliases.json`) | JSON map of bare gc handle → raw Slack target ID — a user (`Uxxxx`/`Wxxxx`) or User Group (`Sxxxx`). The **outbound** inverse of `SLACK_SUBTEAM_ALIAS_FILE`: `/publish` rewrites `@handle` body tokens to Slack mention syntax (`<@Uxxxx>` / ``) so they render as clickable, notifying mentions instead of literal text. Only handles present in the map are rewritten — unmapped handles stay literal (fail-safe, no surprise pings). The `@` is only treated as a mention at a left word boundary, so email-like `user@host` strings are left intact. Hand-edited (operator-curated allowlist); read-only at runtime, SIGHUP or restart to reload. |\n| `INBOUND_FILE_STORE` | `/tmp/gc-slack-adapter/inbound` | Directory for downloaded inbound Slack file attachments. |\n| `INBOUND_FILE_TTL` | `168h` (7 days) | Janitor retention. `0` disables sweeping. |\n| `INBOUND_FILE_SWEEP_INTERVAL` | `1h` | Janitor scan period. `0` disables sweeping. |\n| `SLACK_DISPATCH_CONCURRENCY` | `50` | Cap on in-flight inbound-dispatch goroutines (slash-command, slack-event, alias-resolved). On saturation the adapter drops the dispatch with a `dispatch queue full` log line; the inbound POST itself is not affected. Must be a positive integer; 0/negative/non-numeric values fail at startup. (sec-S-04) |\n\n**Permissions:** `IDENTITY_STORE_PATH`, `HANDLE_ALIAS_STORE_PATH`, and\n`INBOUND_FILE_STORE` are written with `0o700` directories and `0o600`\nfiles so contents are readable only by the adapter's UID. On startup\nthe adapter additionally tightens any pre-existing files/directories\nthat are looser (one-shot migration for legacy `/tmp/gc-slack-adapter/`\ntrees from earlier versions). Operators using a custom-mode parent\n(e.g. setgid for shared-group access) should set the perms before\nadapter start; the tightener preserves setuid/setgid/sticky bits and\nnever loosens an operator-tighter mode. For multi-tenant hosts,\noverride these paths to a host-private location explicitly created\nwith `0o700`. The proxy_process Unix domain socket\n(`GC_SERVICE_SOCKET`) is also chmod'd to `0o600` after bind as\ndefense-in-depth on top of its `0o700` controller-managed parent\ndirectory at `/tmp/gcsvc-//`.\n\n**Controller-injected** (proxy_process mode only — gc sets these when\nthe adapter runs as a `[[service]]`; do not set them by hand):\n`GC_SERVICE_NAME`, `GC_SERVICE_SOCKET`, `GC_SERVICE_URL_PREFIX`,\n`GC_SERVICE_STATE_ROOT`, `GC_SERVICE_RUN_ROOT`. See\n\"What gc injects vs. what stays in the env file\" above.\n\n### SIGHUP-driven reload\n\nFive of the adapter's registries are written by `gc slack` CLI\ncommands and read by the adapter at startup:\n\n| Registry | File | CLI writer |\n| -------------------- | ----------------------------- | ------------------------------- |\n| Apps | `apps.json` | `gc slack import-app` |\n| Channel mappings | `channel_mappings.json` | `gc slack map-channel` |\n| Rig mappings | `rig_mappings.json` | `gc slack map-rig` |\n| Room launch mappings | `room_launch_mappings.json` | `gc slack enable-room-launch` |\n| Subteam aliases | `subteam-aliases.json` | `gc slack sync-subteam-aliases` |\n\nSend `SIGHUP` to the running adapter to pick up CLI-driven changes\nwithout a full restart:\n\n```\npkill -HUP gc-slack-adapter\n```\n\nReload is all-or-nothing across the five files — a single parse\nfailure (corrupt JSON, unknown `target_kind`, missing required field,\nfile >10 MiB) aborts the cycle with the live in-memory state\nuntouched. Errors are logged at WARN; the adapter keeps serving.\n\nA missing file is a **no-op** (live state is preserved). To clear a\nregistry, write an empty JSON object instead of removing the file:\n\n```\necho '{}' > /.gc/slack/channel_mappings.json\npkill -HUP gc-slack-adapter\n```\n\nThe other three registries — `IDENTITY_STORE_PATH`,\n`HANDLE_ALIAS_STORE_PATH`, and the thread-session store — are\n**adapter-owned**: written in-process by the adapter itself (via\n`/identity`, `/handle-alias`, and the launcher), not by the CLI, so\nthey do not participate in SIGHUP reload. Do not hand-edit them while\nthe adapter is running — it rewrites them from its in-memory state at\nwill, so manual edits are silently overwritten. The five files in the\ntable above are the operator-editable surface; everything else under\nthe store paths belongs to the adapter.\n\n`apps.json` is the one file written from **both** sides (the CLI's\n`import-app` and the adapter's OAuth callback). The adapter\ngeneration-stamps its reload snapshots so a SIGHUP racing an OAuth\ncallback re-stages instead of rolling the in-memory view back to the\npre-callback state; if the log shows `apps registry commit skipped`,\nre-send the SIGHUP.\n\n**Consumer-specific** (referenced by deployment scripts and prompts in\nsibling tooling, not by the adapter binary): variables consumed by\n`deliver-rollup.sh`, `resolve_rig_channel.py`, etc., live with the\nconsumer pack and are documented there. The adapter has no opinion on\nwhat handles map to which sessions — that's `/handle-alias` registry\ncontent, set by the consumer at deploy time.\n\n### Slack secrets — placement\n\nRecommended placement for the four must-set vars (and any overrides):\n`${XDG_CONFIG_HOME:-$HOME/.config}/gc-slack-adapter/env` (`0600`\nperms). Source the file before `gc start` so the adapter inherits\nthe env via `os.Environ()`. Under systemd-managed deployments, drop a\nunit override that points\n`EnvironmentFile=-…/gc-slack-adapter/env` at the same path so the\nsupervisor passes the env to its `proxy_process` children. Phase B\nwill move the env-file path into pack config; not yet wired.\n\n```\nSLACK_WORKSPACE_ID=T0XXXXXXXXX\nSLACK_BOT_TOKEN=xoxb-...\nSLACK_SIGNING_SECRET=...\nGC_CITY_NAME=\n```\n\n### Cutover sequence\n\n```\n# 1. Build the adapter binary in place (source colocated with the pack)\n( cd slack-full/adapter && go build -o gc-slack-adapter )\n\n# 2. Source the secrets so the supervisor inherits them\nset -a; source \"${XDG_CONFIG_HOME:-$HOME/.config}/gc-slack-adapter/env\"; set +a\n\n# 3. Stop any manually-managed adapter that may still be running\npkill -f gc-slack-adapter || true\n\n# 4. Reload the city so the [[service]] block from slack-full registers\ngc reload # or: gc supervisor reload\n\n# 5. Verify the service is ready\ngc service list # expect: slack proxy_process ready\ncurl --unix-socket \"$(gc service show slack --json | jq -r .socket)\" \\\n http://x/healthz # expect: 200, first line \"ok\",\n # then dispatch_dropped_total=N\n # (inbound events dropped because\n # the dispatch pool was saturated;\n # a growing N means raise\n # SLACK_DISPATCH_CONCURRENCY)\n\n# 6. Verify outbound publish through gc (replace the placeholders).\n# GC_API_BASE_URL defaults to http://127.0.0.1:8372 on a single-host\n# deployment; GC_CITY_NAME is the directory-name of your city.\ncurl -s -X POST -H \"Content-Type: application/json\" -H \"X-GC-Request: cutover\" \\\n -d '{\"session_id\":\"\",\"conversation\":{\"scope_id\":\"\",\"provider\":\"slack\",\"account_id\":\"T0XXXXXXXXX\",\"conversation_id\":\"D0XXXXXXXXX\",\"kind\":\"dm\"},\"text\":\"*cutover:* hello\"}' \\\n \"${GC_API_BASE_URL:-http://127.0.0.1:8372}/v0/city/${GC_CITY_NAME:-}/extmsg/outbound\" \\\n | jq '.Receipt.Delivered' # expect: true\n\n# 7. Verify inbound — send a Slack DM to the bot, then:\ngc events --city \"${GC_CITY_NAME:-}\" --type extmsg.inbound --since 2m\n```\n\nRollback: remove (or comment out) the `[[service]]` block in\n`pack.toml`, `gc reload`, then restart the manual adapter via the\nlegacy script:\n\n```\n( cd slack-full/adapter \\\n && nohup ./run.sh > /tmp/gc-slack-adapter/run.log 2>&1 & disown )\n```\n\nThe adapter ignores `$GC_SERVICE_SOCKET` when unset and falls back to\nTCP-only mode, so the same binary serves both deployments.\n\n### Known foot-guns\n\n- **Two adapters running.** If you forget step 3 and the manual\n adapter stays up while gc starts the proxy_process one, both will\n call `/extmsg/adapters` to register. The second registration\n overwrites the first; outbound publishes go through whichever one\n registered last (last-write-wins). Symptom: outbound succeeds but\n through the wrong process. Stop the manual one and reload.\n- **Slack signing key missing.** The adapter Fatals at start with\n `missing required env vars: SLACK_SIGNING_SECRET`. Under\n proxy_process this shows up as the service stuck in `degraded`\n state with the env-var name in the reason field. Source the env\n file in the supervisor's launching shell.\n- **Funnel rule out-of-band.** Tailscale Funnel `:443 → :8775` is not\n declared in the city. If you reboot the host or `tailscale funnel\nreset`, traffic stops landing at the adapter. Re-add the rule\n manually until Phase C lands.\n\n## Where the work that's still missing comes from\n\nThe discord pack ships ~350K LOC of Python service code; the bulk of\nthat is provider-agnostic state-machine logic for room peer fanout,\nlauncher mode, slash-command intake, and workflow status projection.\nA meaningful chunk should eventually become a shared `extmsg-pack-lib`\nor similar so slack/discord/teams/etc. don't all reimplement it.\n\nFor now we're porting feature-by-feature, against real usage from the\noversight-rig pack." + "content": "# slack-full (v0.0.1 preview — scaffold)\n\nA Slack provider extension for Gas City. Modeled directly on the\nupstream `discord` pack\n(https://github.com/gastownhall/gascity-packs/tree/main/discord) so\nthe same primitives can be ported one at a time.\n\nThis pack lives at `slack-full/` in the\n[`gastownhall/gascity-packs`](https://github.com/gastownhall/gascity-packs)\ncatalog. A city opts in by importing it from that repository (see\n\"Install\" below).\n\n## Tiering\n\n`slack-full` is **Tier 3** of the Slack pack family — workspace-grade\norchestration. It is the original, full-surface pack; the two smaller\ntiers were later extracted from it for cities that need less.\n\n- **[slack-mini](../slack-mini)** (Tier 1) — single-file adapter, one\n outbound verb (`gc slack-mini post-message`), `app_mention` → mayor.\n No channel bindings, no state. Reach for it when you only need the\n mayor to speak into one channel.\n- **[slack-channel](../slack-channel)** (Tier 2) — the \"team channel ↔\n session graph\" middle tier: channel bindings, per-session identities,\n handle aliases, reactions, threaded replies. No multi-rig routing,\n slash commands, peer fanout, or launcher mode.\n- **slack-full** (Tier 3, this pack) — workspace-grade orchestration:\n multi-rig routing, slash commands + interactions, peer fanout,\n launcher mode, bidirectional file attachments, and per-session\n identity overrides.\n\n### Choosing a tier\n\n- Need the mayor to post status into a single channel and nothing\n else? → **slack-mini**.\n- Need a handful of named sessions sharing one or more channels with\n distinct identities, but no slash commands or cross-rig routing? →\n **slack-channel**.\n- Need slash commands, interactive modals/buttons, peer fanout across a\n room, launcher-mode session spawning, or multi-rig channel routing? →\n **slack-full** (this pack).\n\nSee the [Slack pack tiering design memo](../docs/design/slack-pack-tiering.md)\nfor the full rationale.\n\n> **Scope: not yet at parity with the discord pack.** The discord pack\n> ships ~350K LOC of provider-agnostic Python state-machine logic.\n> Slack pack is a feature-by-feature port; today's surface is enough\n> to run a multi-session oversight loop end-to-end (DMs + rooms +\n> peer fanout + identity overrides + bidirectional file\n> attachments), but several discord-pack features are still missing\n> (see \"Not yet implemented\" below). If you need parity today, use\n> the discord pack.\n\n## Status\n\nImplemented:\n\n- [x] `gc slack bind-dm` — bind a Slack DM channel to one named session\n- [x] `gc slack bind-room` — bind a room to multiple sessions; requires\n exactly one of `--binding-owner SESSION` / `--group-only` to\n declare the room's binding shape; further flags\n `--enable-peer-fanout`, `--allow-untargeted-publication`,\n `--max-peer-triggered-publishes`, `--max-total-peer-deliveries`,\n `--default-handle`, `--handle HANDLE=SESSION` (creates a\n launcher-mode group + participants under the hood)\n- [x] `gc slack reply-current` — reply to the latest Slack event in the\n current session, by default through gc's `/extmsg/outbound` so\n transcript recording + peer fanout fire (`--via adapter` keeps the\n old direct-to-adapter path for diagnostics). A thread-reply\n inbound is answered in its thread by default (`--no-thread`\n forces a channel-level post)\n- [x] `gc slack publish` — publish to a session's saved binding (target\n session required, no event-scan fallback — fail-fast when the\n session has no active binding)\n- [x] `gc slack publish-to-channel` — publish to an arbitrary channel\n ID (no session binding required; useful for one-shot ops posts)\n- [x] `gc slack status` — read-only diagnostics (adapters, bindings,\n recent traffic). `--session SID` for one-session detail,\n `--since 5m` for a time window (default `168h`; `--since ''`\n reads the full history), `--json` for scripting. Exits 0\n when every section was read, 2 when any section could not be;\n an unreadable section is marked `(UNREADABLE: )` rather\n than being shown as empty, and the readable sections still print.\n- [x] `gc slack react` — add an emoji reaction to a Slack message\n- [x] `gc slack identity` — register/unregister a per-session\n `chat:write.customize` identity (display name + icon) so each\n bound session posts under its own persona\n- [x] `gc slack handle-alias` — register/unregister a cross-channel\n `@handle` → session-id alias used by the address-by-handle\n protocol\n- [x] `gc slack upload` — bidirectional file attachments\n (`/publish-file` outbound + auto-download of inbound files into\n `$INBOUND_FILE_STORE//-`, scrubbed by an\n in-process retention janitor)\n- [x] `template-fragments/slack-v0.template.md` — composable prompt\n fragment for any agent in a slack-bound session\n- [x] Pack-owned intake service (`[[service]]` proxy_process). Phase A:\n adapter is the same Go binary, but gc supervises it via UDS for\n `/publish` while the public Slack webhook still terminates at\n adapter TCP `:8775` (Funnel unchanged). See \"Adapter as a\n proxy_process service\" below for the cutover.\n\nSlash-command / launcher / projection surface (operator CLI verbs):\n\n- [x] `gc slack import-app` — register a Slack app manifest with the gc\n city ([`manifest/README.md`](./manifest/README.md#importing-into-gc-gc-slack-import-app))\n- [x] `gc slack map-channel` — bind a Slack channel to a session;\n backs the adapter's `/slack/interactions` slash-command\n dispatcher. (Note: the legacy `--rig` flag on this verb is\n deprecated as of gc-cby.25; use `gc slack map-rig` for\n rig→channel bindings. The flag still works for back-compat\n and emits a stderr deprecation warning.)\n- [x] `gc slack map-rig` — bind a rig to a set of channels as the\n fall-through default for slash-command resolution. Per-channel\n `map-channel --session` bindings override rig defaults; channel\n mapping wins. Cross-store conflict detection refuses contradictory\n writes in either direction. `--remove` drops the entire rig\n record; `--remove-channels c1,c2` drops just the listed channels\n (the record itself is deleted if the set becomes empty). Both\n removal paths are idempotent.\n- [x] `gc slack sync-commands` — register/refresh the city's slash\n commands against the Slack app (read by the adapter's\n `/slack/interactions` dispatcher).\n- [x] `gc slack sync-subteam-aliases` — reconcile\n `/.gc/slack/subteam-aliases.json` (the Slack User Group\n (\"subteam\") id → gc handle map) with the workspace's live User\n Groups via Slack `usergroups.list`. Like `sync-commands`, it diffs\n live against on-disk and supports `--dry-run` and\n `--output text|json`; unlike it, the write is a non-destructive\n MERGE — existing entries are preserved, handles are refreshed where\n the live name differs, new User Groups are added, and entries with\n no matching live group are kept and reported as \"local-only\" rather\n than dropped. The bot token (`--token` / `$SLACK_BOT_TOKEN`) must\n carry the `usergroups:read` scope; without it the verb reports a\n readable `missing_scope` error and writes nothing. After a write it\n prints the same SIGHUP reminder as the other registry-writing verbs.\n- [x] `gc slack post-message` — post (or `--update `) a Block Kit\n message to a channel via `chat.postMessage`/`chat.update`. The\n one-shot verb is shipped; automatic workflow-status projection\n from gc events is not yet wired.\n- [x] `gc slack retry-peer-fanout` (walks `extmsg.peer_fanout_failed`\n events, dedupes against successful `extmsg.peer_fanout_retried`\n events, re-issues each notification via the gc retry endpoint with\n a small cooldown between attempts)\n\nNot yet implemented (planned):\n\n- [ ] `gc slack enable-room-launch` — the CLI verb writes the\n `(workspace_id, channel_id) → pool_template` mapping today, but the\n adapter-side `@@` thread-scoped session spawn is still a\n stub: an `@@` post in an enabled channel currently gets an\n ephemeral \"launcher not yet available\" reply rather than spawning a\n session (tracked under the gc-cby epic).\n\nThe adapter exposes `POST /slack/interactions` (HMAC-verified) for\nslash-command, `block_actions`, and `view_submission` dispatch.\n\n`block_actions` (button clicks, select-menu finalizations, datepickers,\netc.) routes through the same channel-binding flow as slash commands:\nthe action's originating channel (`payload.channel.id` falling back to\n`payload.container.channel_id`) must be bound via `gc slack\nmap-channel` (session target) or `gc slack map-rig` (rig target — the\nrig branch is recorded but routing is deferred to gc-cby.18).\n\n`view_submission` (modal submits) carries no channel context, so the\nmodal opener MUST set `view.private_metadata` to the JSON string\n`{\"session_id\":\"\"}` when calling `views.open`/`views.push`.\nOn submit, the adapter strict-decodes that field (extra keys rejected,\nsession_id length-capped) and posts a system-reminder to that session\ndescribing the `callback_id` plus `view.state.values`. Any decode\nfailure responds `{\"response_action\":\"clear\"}` so Slack closes the\nmodal stack and the user knows the submit did not process.\n\nThe adapter's workspace gate (`SLACK_WORKSPACE_ID` / `cfg.accountID`)\napplies on both branches — `payload.team.id` must match. `response_url`\n(valid ~30 minutes / 5 uses on `block_actions`) is forwarded to the\nagent in the system-reminder; persistent storage of `response_url` for\nlater use is out of scope.\n\nOther Slack interaction types (`shortcut`, `message_action`,\n`view_closed`, `block_suggestion`) return an ephemeral \"unsupported\ninteraction type\" reply — they need separate routing logic and are\ntracked under the gc-cby epic.\n\n## Binaries\n\nThis pack ships **two** Go binaries, each with its own `go.mod` so\neither can travel intact when the pack is mirrored upstream:\n\n- **Adapter** — `slack-full/adapter/gc-slack-adapter`,\n built from `adapter/main.go`. The public-facing webhook receiver\n (Slack → `:8775` over Tailscale Funnel) and outbound publisher\n (`/publish` over the controller-managed UDS). Long-running; gc\n supervises it via the `[[service]]` block in `pack.toml`.\n\n- **Operator CLI** — `slack-full/cli/gc-slack-cli`, built\n from `cli/main.go` + `cli/cmd/`. Backs the `gc slack ` verb\n surface (import-app, map-channel, map-rig, sync-commands,\n sync-subteam-aliases, enable-room-launch, post-message). One-shot per invocation; not\n supervised. The pack's `commands/.sh` wrappers exec it at\n `$GC_PACK_DIR/cli/gc-slack-cli` so operator command-line ergonomics\n stay identical to the pre-relocation gc-binary verbs.\n\nBuild commands and CI for both binaries are documented in\n[CONTRIBUTING.md](./CONTRIBUTING.md#build-flow).\n\n## Architecture (current)\n\nThe Go adapter at `slack-full/adapter/gc-slack-adapter`\n(built from `adapter/main.go` colocated with this pack) is the\npublic-facing webhook receiver and outbound publisher. This\npack adds CLI surface around it: `bind-dm` writes to gc's\n`/extmsg/bind` and to the pack's local config; `reply-current` reads\nrecent gc events to find the conversation, then POSTs to gc's\n`/extmsg/outbound` (which calls the registered HTTP adapter's\n`/publish` endpoint internally and emits `ExtMsgOutbound` so peer\nfanout fires for bind-room sessions). `--via adapter` is available\nfor adapter-only diagnostics that bypass gc.\n\n```\n ┌──── public ────┐\nSlack ──HMAC──▶ Go adapter :8775 ──▶ gc /extmsg/inbound\n Go adapter :8766 ◀── gc /extmsg/outbound ◀── gc slack reply-current\n ◀── (--via adapter) ─────── gc slack reply-current\n └────────────────┘\n```\n\nThe `:8775` public port above is the reference Funnel-fronted\ndeployment (`LISTEN_PUBLIC=:8775`, with a Tailscale Funnel rule\n`:443 → :8775`). The bare adapter default is `:8765` — see the\n`LISTEN_PUBLIC` row in \"Adapter env contract\" below.\n\nThe adapter's `/publish` endpoint **requires session attribution**: every\nrequest must carry a `session_id` (or, for older gc binaries, the legacy\n`metadata.source_session_id`). A request with neither is rejected with HTTP\n400 and nothing is posted to Slack:\n\n```json\n{\"error\": \"publish requires session attribution: provide session_id or metadata.source_session_id\"}\n```\n\nThis fails closed against identity-less channel-root posts under the default\nbot identity. System/bot status notifications that intentionally post as the bot\ngo through `gc slack post-message` (direct to Slack `chat.postMessage`),\nwhich bypasses `/publish` entirely and is unaffected by this guard.\n\n## Install\n\nImport the pack into your city:\n\n```sh\ngc import add https://github.com/gastownhall/gascity-packs.git//slack-full\n```\n\nThat writes the import and pins the release in `packs.lock`. The same import\nby hand in `pack.toml`, followed by `gc import install`:\n\n```toml\n[imports.slack-full]\nsource = \"https://github.com/gastownhall/gascity-packs.git//slack-full\"\n```\n\n`gc pack registry show slack-full` prints the registry entry and its releases.\nTo work on the pack from a local checkout, point `source` at the path\ninstead:\n\n```toml\n[imports.slack-full]\nsource = \"/path/to/gascity-packs/slack-full\"\n```\n\nThen `gc reload` (or wait for the supervisor to pick up the change).\nVerify the commands appear:\n\n```\ngc slack --help\ngc slack bind-dm --help\ngc slack reply-current --help\n```\n\n## Verify\n\nAssuming the adapter is up and the four must-set vars\n(`SLACK_WORKSPACE_ID`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`,\n`GC_CITY_NAME`) are exported (sourced from\n`${XDG_CONFIG_HOME:-$HOME/.config}/gc-slack-adapter/env` — see\n\"Adapter env contract\" below):\n\n```\n# Bind a DM channel to a session. Replace D0XXXXXXXXX with the DM\n# channel ID Slack assigned to your bot's IM with the human user.\ngc slack bind-dm D0XXXXXXXXX my-session.cos\n\n# From inside that session (or any session that has seen recent\n# extmsg.inbound on a slack conversation):\necho \"*my-session.cos:* ack via slack pack\" > /tmp/reply.txt\ngc slack reply-current --body-file /tmp/reply.txt\n```\n\nThe reply should land in your Slack DM.\n\nTo bind a room (public or private channel) to multiple sessions so\nthat two or more agents are visible peers and a human can join the\nconversation:\n\n```\ngc slack bind-room C0XXXXXXXXX \\\n my-pack.session-a my-pack.session-b \\\n --enable-peer-fanout \\\n --binding-owner my-pack.session-b\n```\n\nBoth sessions then receive an inbound system reminder for every human\nmessage in the channel; `extmsg.inbound` events list both as\nconversation members. When the publishing session calls\n`gc slack reply-current` (default `--via gc`), gc records the publish\nin the conversation transcript and fans out a peer-publication\nreminder to the other bound sessions so they see what their peer just\nsaid.\n\n`--binding-owner SESSION` is what makes outbound publishes (and\ntherefore `gc slack reply-current --via gc`) actually work: without a\ndirect binding, `/extmsg/outbound` has no `SessionBindingRecord` to\nresolve the conversation through and the publish is rejected, even\nthough peer fanout still fires on inbound. The owner must be one of\nthe participants — prefer the session that \"owns\" the room from gc's\nperspective. Pass the gc session id (e.g. `gc-77139`) when alias\nresolution semantics matter; for stable named sessions, the alias\nworks too.\n\nEvery run must declare which of the two shapes it wants: pass\n`--binding-owner SESSION` for a room with an outbound publisher, or\n`--group-only` for a purely group-routed room. `--group-only` removes\n*every* active direct binding for the conversation — including ones\ncreated by other tooling — so that none can shadow the group route,\nand it prints each removed binding to stderr. Because that removal is\ndestructive, it is never the default: a run with neither flag is\nrejected before any API call rather than quietly sweeping the room's\npublisher. Re-running `bind-room` on an owned room therefore has to\nname the owner again.\n\n### Threaded replies\n\nWhen a human replies inside a Slack thread, the text the session\nreceives starts with a `[slack thread_ts=]` line naming the\nthread root. After it comes the earlier thread context: the root\nmessage's text on every reply, even when a bot posted it (shown as\n`[bot ]: ...`), plus any human messages newer than the last\nreply this session already received. Other bot messages, and messages\nwith no text (a file-only root, for example), are left out. If the adapter cannot fetch the thread from Slack, the\nmarker line is followed by `Thread parent could not be fetched.`\ninstead of the context. `gc slack reply-current` already answers in\nthe thread; with `gc slack publish-to-channel`, pass the root ts as\n`--thread-ts`. Messages outside a thread arrive without the marker.\n\n## Adapter as a proxy_process service\n\nPhase A of the in-pack adapter (tracked as bd `gc-5rz`) lets gc\nsupervise the adapter as part of the city's services. The adapter\nbinds a Unix domain socket for the `/publish` endpoint that gc reaches\nvia the extmsg HTTP adapter, and gc reverse-proxies `/svc/slack/*` to\nthat UDS. The public Slack webhook (`/slack/events`) still terminates\nat the adapter's TCP `:8775` so Tailscale Funnel and Slack's signing\nsecret verification are unchanged. The same binary runs in both modes\n— the legacy `nohup ./run.sh` deployment is preserved as a rollback\ntarget.\n\n### What gc injects vs. what stays in the env file\n\n`proxy_process` injects the controller-managed env at start time:\n\n- `GC_SERVICE_NAME=slack`\n- `GC_SERVICE_SOCKET=/tmp/gcsvc-//slack-*.sock`\n- `GC_SERVICE_URL_PREFIX=/svc/slack`\n- `GC_SERVICE_STATE_ROOT=.../.gc/services/slack`\n- `GC_SERVICE_RUN_ROOT=.../.gc/services/slack/run`\n\nNote: `GC_API_BASE_URL` and `GC_CITY_NAME` are NOT injected by the\ncontroller for `proxy_process` services — they must be present in the\nenv file you source before `gc start`, so the supervisor inherits them\nand passes them down to the spawned adapter.\n\nWhen `GC_SERVICE_SOCKET` is set, the adapter:\n- skips its `LISTEN_INTERNAL` TCP listener and binds the UDS instead;\n- computes its self-registration `CallbackURL` as\n `$GC_API_BASE_URL + $GC_SERVICE_URL_PREFIX` (gc's extmsg HTTP adapter\n appends `/publish` itself when calling out, so the registered base\n URL must NOT include `/publish`);\n- still binds public TCP for `/slack/events`;\n- still serves `/healthz` on the UDS so the controller's `health_path`\n probe succeeds.\n\n### Adapter env contract\n\nThe full adapter env contract — what the binary at\n`slack-full/adapter/main.go` reads — is enumerated in the\npackage docstring at the top of that file. Summary:\n\n**Must-set** (no default; adapter exits at startup if missing):\n\n| Var | Purpose |\n| ---------------------- | ------------------------------------------------------------------ |\n| `SLACK_WORKSPACE_ID` | Slack team id (e.g. `T0XXXXXXXXX`). |\n| `SLACK_BOT_TOKEN` | `xoxb-…` token. Scopes: `chat:write`, `reactions:write`, `files:write`, and `chat:write.customize` for per-session identity overrides. |\n| `SLACK_SIGNING_SECRET` | Used to verify HMAC signatures on `/slack/events` requests. |\n| `GC_CITY_NAME` | gc city the adapter posts inbound + session-message traffic to. Matches `[workspace].name` in `city.toml`. No fallback default — the adapter fails fast rather than silently route to a wrong city. |\n\n**Optional override** (sane default; set to override):\n\n| Var | Default | Purpose |\n| ------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------- |\n| `LISTEN_PUBLIC` | `:8765` | Public listener for `/slack/events` (bind `0.0.0.0` if fronted by a tunnel). |\n| `LISTEN_INTERNAL` | `127.0.0.1:8766` | Loopback listener for `/publish`. Ignored under proxy_process mode. |\n| `INTERNAL_CALLBACK_URL` | `http://127.0.0.1:8766` | URL advertised to gc at self-registration. Ignored under proxy_process mode. |\n| `GC_API_BASE_URL` | `http://127.0.0.1:9443` | Base URL for gc's HTTP API. |\n| `ADAPTER_PROVIDER` | `slack` | Provider name in conversation refs + adapter registration. |\n| `REGISTER_ON_START` | `true` | Set `false` to skip `/extmsg/adapters` registration (tests, diagnostics). |\n| `HANDLE_PREFIX` | `@` | Leading address token for keyword routing. Empty disables routing. |\n| `IDENTITY_STORE_PATH` | `/tmp/gc-slack-adapter/identities.json` | JSON file backing the per-session `chat:write.customize` identity registry. |\n| `HANDLE_ALIAS_STORE_PATH` | `/tmp/gc-slack-adapter/handle-aliases.json` | JSON file backing the cross-channel handle → session-id alias registry. |\n| `SLACK_SUBTEAM_ALIAS_FILE` | `/.gc/slack/subteam-aliases.json` (or `/tmp/gc-slack-adapter/subteam-aliases.json`) | JSON map of Slack User Group (\"subteam\") IDs → gc handles, hand-edited or written by `gc slack sync-subteam-aliases`. Required to route the unlabeled `` mention shape; the labeled `` / `` shape is gated by `HANDLE_ALIAS_STORE_PATH` instead. Read-only at runtime; SIGHUP or restart to reload. |\n| `SLACK_USER_ALIAS_FILE` | `/.gc/slack/slack-user-aliases.json` (or `/tmp/gc-slack-adapter/slack-user-aliases.json`) | JSON map of bare gc handle → raw Slack target ID — a user (`Uxxxx`/`Wxxxx`) or User Group (`Sxxxx`). The **outbound** inverse of `SLACK_SUBTEAM_ALIAS_FILE`: `/publish` rewrites `@handle` body tokens to Slack mention syntax (`<@Uxxxx>` / ``) so they render as clickable, notifying mentions instead of literal text. Only handles present in the map are rewritten — unmapped handles stay literal (fail-safe, no surprise pings). The `@` is only treated as a mention at a left word boundary, so email-like `user@host` strings are left intact. Hand-edited (operator-curated allowlist); read-only at runtime, SIGHUP or restart to reload. |\n| `INBOUND_FILE_STORE` | `/tmp/gc-slack-adapter/inbound` | Directory for downloaded inbound Slack file attachments. |\n| `INBOUND_FILE_TTL` | `168h` (7 days) | Janitor retention. `0` disables sweeping. |\n| `INBOUND_FILE_SWEEP_INTERVAL` | `1h` | Janitor scan period. `0` disables sweeping. |\n| `SLACK_DISPATCH_CONCURRENCY` | `50` | Cap on in-flight inbound-dispatch goroutines (slash-command, slack-event, alias-resolved). On saturation the adapter drops the dispatch with a `dispatch queue full` log line; the inbound POST itself is not affected. Must be a positive integer; 0/negative/non-numeric values fail at startup. (sec-S-04) |\n\n**Permissions:** `IDENTITY_STORE_PATH`, `HANDLE_ALIAS_STORE_PATH`, and\n`INBOUND_FILE_STORE` are written with `0o700` directories and `0o600`\nfiles so contents are readable only by the adapter's UID. On startup\nthe adapter additionally tightens any pre-existing files/directories\nthat are looser (one-shot migration for legacy `/tmp/gc-slack-adapter/`\ntrees from earlier versions). Operators using a custom-mode parent\n(e.g. setgid for shared-group access) should set the perms before\nadapter start; the tightener preserves setuid/setgid/sticky bits and\nnever loosens an operator-tighter mode. For multi-tenant hosts,\noverride these paths to a host-private location explicitly created\nwith `0o700`. The proxy_process Unix domain socket\n(`GC_SERVICE_SOCKET`) is also chmod'd to `0o600` after bind as\ndefense-in-depth on top of its `0o700` controller-managed parent\ndirectory at `/tmp/gcsvc-//`.\n\n**Controller-injected** (proxy_process mode only — gc sets these when\nthe adapter runs as a `[[service]]`; do not set them by hand):\n`GC_SERVICE_NAME`, `GC_SERVICE_SOCKET`, `GC_SERVICE_URL_PREFIX`,\n`GC_SERVICE_STATE_ROOT`, `GC_SERVICE_RUN_ROOT`. See\n\"What gc injects vs. what stays in the env file\" above.\n\n### SIGHUP-driven reload\n\nFive of the adapter's registries are written by `gc slack` CLI\ncommands and read by the adapter at startup:\n\n| Registry | File | CLI writer |\n| -------------------- | ----------------------------- | ------------------------------- |\n| Apps | `apps.json` | `gc slack import-app` |\n| Channel mappings | `channel_mappings.json` | `gc slack map-channel` |\n| Rig mappings | `rig_mappings.json` | `gc slack map-rig` |\n| Room launch mappings | `room_launch_mappings.json` | `gc slack enable-room-launch` |\n| Subteam aliases | `subteam-aliases.json` | `gc slack sync-subteam-aliases` |\n\nSend `SIGHUP` to the running adapter to pick up CLI-driven changes\nwithout a full restart:\n\n```\npkill -HUP gc-slack-adapter\n```\n\nReload is all-or-nothing across the five files — a single parse\nfailure (corrupt JSON, unknown `target_kind`, missing required field,\nfile >10 MiB) aborts the cycle with the live in-memory state\nuntouched. Errors are logged at WARN; the adapter keeps serving.\n\nA missing file is a **no-op** (live state is preserved). To clear a\nregistry, write an empty JSON object instead of removing the file:\n\n```\necho '{}' > /.gc/slack/channel_mappings.json\npkill -HUP gc-slack-adapter\n```\n\nThe other three registries — `IDENTITY_STORE_PATH`,\n`HANDLE_ALIAS_STORE_PATH`, and the thread-session store — are\n**adapter-owned**: written in-process by the adapter itself (via\n`/identity`, `/handle-alias`, and the launcher), not by the CLI, so\nthey do not participate in SIGHUP reload. Do not hand-edit them while\nthe adapter is running — it rewrites them from its in-memory state at\nwill, so manual edits are silently overwritten. The five files in the\ntable above are the operator-editable surface; everything else under\nthe store paths belongs to the adapter.\n\n`apps.json` is the one file written from **both** sides (the CLI's\n`import-app` and the adapter's OAuth callback). The adapter\ngeneration-stamps its reload snapshots so a SIGHUP racing an OAuth\ncallback re-stages instead of rolling the in-memory view back to the\npre-callback state; if the log shows `apps registry commit skipped`,\nre-send the SIGHUP.\n\n**Consumer-specific** (referenced by deployment scripts and prompts in\nsibling tooling, not by the adapter binary): variables consumed by\n`deliver-rollup.sh`, `resolve_rig_channel.py`, etc., live with the\nconsumer pack and are documented there. The adapter has no opinion on\nwhat handles map to which sessions — that's `/handle-alias` registry\ncontent, set by the consumer at deploy time.\n\n### Slack secrets — placement\n\nRecommended placement for the four must-set vars (and any overrides):\n`${XDG_CONFIG_HOME:-$HOME/.config}/gc-slack-adapter/env` (`0600`\nperms). Source the file before `gc start` so the adapter inherits\nthe env via `os.Environ()`. Under systemd-managed deployments, drop a\nunit override that points\n`EnvironmentFile=-…/gc-slack-adapter/env` at the same path so the\nsupervisor passes the env to its `proxy_process` children. Phase B\nwill move the env-file path into pack config; not yet wired.\n\n```\nSLACK_WORKSPACE_ID=T0XXXXXXXXX\nSLACK_BOT_TOKEN=xoxb-...\nSLACK_SIGNING_SECRET=...\nGC_CITY_NAME=\n```\n\n### Cutover sequence\n\n```\n# 1. Build the adapter binary in place (source colocated with the pack).\n# Optional: the [[service]] command is adapter/run.sh, which rebuilds\n# gc-slack-adapter itself when the binary is missing (e.g. after\n# `gc import install` re-materialized the pack cache git-only).\n( cd slack-full/adapter && go build -o gc-slack-adapter )\n\n# 2. Source the secrets so the supervisor inherits them.\n# Optional: run.sh sources this same file itself at service start\n# (override the path with GC_SLACK_ADAPTER_ENV — if you set it, the\n# file must exist: run.sh refuses to start on ambient credentials\n# rather than post to whatever workspace is in the environment).\nset -a; source \"${XDG_CONFIG_HOME:-$HOME/.config}/gc-slack-adapter/env\"; set +a\n\n# 3. Stop any manually-managed adapter that may still be running\npkill -f gc-slack-adapter || true\n\n# 4. Reload the city so the [[service]] block from slack-full registers\ngc reload # or: gc supervisor reload\n\n# 5. Verify the service is ready\ngc service list # expect: slack proxy_process ready\ncurl --unix-socket \"$(gc service show slack --json | jq -r .socket)\" \\\n http://x/healthz # expect: 200, first line \"ok\",\n # then dispatch_dropped_total=N\n # (inbound events dropped because\n # the dispatch pool was saturated;\n # a growing N means raise\n # SLACK_DISPATCH_CONCURRENCY)\n\n# 6. Verify outbound publish through gc (replace the placeholders).\n# GC_API_BASE_URL defaults to http://127.0.0.1:8372 on a single-host\n# deployment; GC_CITY_NAME is the directory-name of your city.\ncurl -s -X POST -H \"Content-Type: application/json\" -H \"X-GC-Request: cutover\" \\\n -d '{\"session_id\":\"\",\"conversation\":{\"scope_id\":\"\",\"provider\":\"slack\",\"account_id\":\"T0XXXXXXXXX\",\"conversation_id\":\"D0XXXXXXXXX\",\"kind\":\"dm\"},\"text\":\"*cutover:* hello\"}' \\\n \"${GC_API_BASE_URL:-http://127.0.0.1:8372}/v0/city/${GC_CITY_NAME:-}/extmsg/outbound\" \\\n | jq '.Receipt.Delivered' # expect: true\n\n# 7. Verify inbound — send a Slack DM to the bot, then:\ngc events --city \"${GC_CITY_NAME:-}\" --type extmsg.inbound --since 2m\n```\n\nRollback: remove (or comment out) the `[[service]]` block in\n`pack.toml`, `gc reload`, then restart the manual adapter via the\nlegacy script:\n\n```\n( cd slack-full/adapter \\\n && nohup ./run.sh > /tmp/gc-slack-adapter/run.log 2>&1 & disown )\n```\n\nThe adapter ignores `$GC_SERVICE_SOCKET` when unset and falls back to\nTCP-only mode, so the same binary serves both deployments.\n\n### Known foot-guns\n\n- **Two adapters running.** If you forget step 3 and the manual\n adapter stays up while gc starts the proxy_process one, both will\n call `/extmsg/adapters` to register. The second registration\n overwrites the first; outbound publishes go through whichever one\n registered last (last-write-wins). Symptom: outbound succeeds but\n through the wrong process. Stop the manual one and reload.\n- **Slack signing key missing.** The adapter Fatals at start with\n `missing required env vars: SLACK_SIGNING_SECRET`. Under\n proxy_process this shows up as the service stuck in `degraded`\n state with the env-var name in the reason field. Source the env\n file in the supervisor's launching shell.\n- **Funnel rule out-of-band.** Tailscale Funnel `:443 → :8775` is not\n declared in the city. If you reboot the host or `tailscale funnel\nreset`, traffic stops landing at the adapter. Re-add the rule\n manually until Phase C lands.\n\n## Where the work that's still missing comes from\n\nThe discord pack ships ~350K LOC of Python service code; the bulk of\nthat is provider-agnostic state-machine logic for room peer fanout,\nlauncher mode, slash-command intake, and workflow status projection.\nA meaningful chunk should eventually become a shared `extmsg-pack-lib`\nor similar so slack/discord/teams/etc. don't all reimplement it.\n\nFor now we're porting feature-by-feature, against real usage from the\noversight-rig pack." }, "og_image": "/og/slack-full.svg", - "latest": "0.0.3", + "latest": "0.0.5", "releases": [ { "version": "0.0.2", @@ -528,6 +544,22 @@ "hash": "sha256:2d2255e68175b7d464845100654839a0956e8cd6fefa259e85cd03915213186b", "description": "Ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark pack scripts/tests executable to align release hash with materialized modes.", "withdrawn": false + }, + { + "version": "0.0.4", + "ref": "main", + "commit": "ea4066504ef753c0b4e8bcc73aea2d447c964969", + "hash": "sha256:00d3af738603107cc507136d9ed3cb3f3e88f3f4834e88a98cd5b0564872cbb1", + "description": "Rolls up main since 0.0.3: company rooms, per-agent and group DMs, delivery that wakes sleeping sessions, a run.sh entrypoint that rebuilds a missing adapter, thread-root context on every threaded reply, app manifests that Slack accepts on import, and gc slack status reporting unreadable sections instead of empty ones.", + "withdrawn": false + }, + { + "version": "0.0.5", + "ref": "main", + "commit": "8e95b894d2b24c1aa9b59688e587e5914a169e47", + "hash": "sha256:9aabd24333cd1790b3b60ef50349e2a874c07ba967433fa21caf9034fa51ea4d", + "description": "The adapter start script builds in private per-user directories when HOME is unset, instead of shared fixed paths under TMPDIR that another local user could create first.", + "withdrawn": false } ] }, @@ -540,13 +572,13 @@ "description": "Minimal Slack mention bridge and outbound message pack for Gas City.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/slack-mini", "source_kind": "git", - "search_text": "slack-mini gascity-packs maintained gas city minimal slack mention bridge and outbound message pack for gas city. https://github.com/gastownhall/gascity-packs/tree/main/slack-mini git # slack-mini **talk to your gas city mayor from slack.** add a bot to your workspace, `@`-mention it from any channel, and the mention bridges to your gc mayor session. reply back with one verb. slack-mini is **tier 1** of the slack pack family — the smallest surface that gets a human talking to gc over slack. it ships a single-file adapter and one outbound verb. no channel bindings, no per-session identity, no multi-rig routing — those live in `slack-channel` (tier 2) and `slack-full` (tier 3). see the [slack-pack tiering design memo](../docs/design/slack-pack-tiering.md) (landing separately). > pick exactly one slack tier per city. the tiers are alternatives, not > layers you stack. ## what you get - **inbound:** `@gc-mayor what's the convoy status?` in any channel the bot is in → delivered to your gc mayor session. - **outbound:** `gc slack-mini post-message --channel c0123 --text \"…\"` → posts to a channel, optionally in a thread. ## install in 3 minutes ### 1. create the slack app (1 min) create an app from the shipped manifest at [`manifest/app.json`](./manifest/app.json): 1. → **create new app** → **from a manifest**. 2. pick your workspace, paste `manifest/app.json`, create. 3. **install to workspace** and copy the **bot user oauth token** (`xoxb-…`). 4. from **basic information**, copy the **signing secret**. the manifest requests only three scopes — `app_mentions:read`, `chat:write`, `chat:write.public` — and subscribes to the `app_mention` event. ### 2. configure the city (1 min) import the pack in your city's `pack.toml`: ```toml [imports.slack-mini] source = \"../packs/slack-mini\" ``` provide the adapter's environment (e.g. in your city service env or `~/.config/gc-slack-mini-adapter/env`): ```sh slack_bot_token=xoxb-... # from step 1 slack_signing_secret=... # from step 1 slack_workspace_id=t0123abcd # your slack team id gc_city_name= # the gc city to bridge into ``` that's the full required set — four variables. ### 3. expose the events endpoint and start (1 min) the adapter listens for slack events on public tcp (`listen_public`, default `0.0.0.0:8775`). terminate tls in front of it and give slack a public url — [tailscale funnel](https://tailscale.com/kb/1223/funnel) is the easy path: ```sh tailscale funnel 8775 ``` then in the slack app's **event subscriptions**, set the request url to `https:///slack/events`. slack sends a one-time `url_verification` challenge, which the adapter answers automatically. gc supervises the adapter as a `proxy_process` service (named `slack-mini`); building the binary is a one-time `go build` in `adapter/` (see [build](#build)). start your city and `@`-mention the bot. ## replying in a thread when the mayor session handles an inbound mention, the conversation carries the slack message `ts` as its reply-to id. answer in the same thread with: ```sh gc slack-mini post-message --channel c0123 \\ --thread-ts 1700000000.0001 --text \"on it — see pr #42\" ``` ## build the adapter is a single go file with no external dependencies: ```sh cd adapter go build -o gc-slack-mini-adapter ./... ``` the built binary is git-ignored; the `[[service]]` block runs it in place. ## configuration reference | variable | required | default | purpose | | --- | :---: | --- | --- | | `slack_bot_token` | ✓ | — | bot token for `chat.postmessage` and the inbound post. | | `slack_signing_secret` | ✓ | — | hmac secret for verifying slack requests. | | `slack_workspace_id` | ✓ | — | slack team id (the extmsg account id). | | `gc_city_name` | ✓ | — | gc city to bridge into. | | `listen_public` | | `0.0.0.0:8775` | public bind for `/slack/events`. | | `listen_internal` | | `127.0.0.1:8776` | tcp bind for `/post-message` when not run as a gc proxy_process (no `gc_service_socket`). | | `register_on_start` | | `true` | self-register as an extmsg adapter on start. | | `slack_mini_inbound_target` | | `mayor` | session handle inbound mentions address. | | `slack_api_base` | | `https://slack.com/api` | slack web api origin (override for relays/tests). | | `gc_api_base_url` | | `http://127.0.0.1:9443` | gc api base. | `gc_service_socket`, `gc_service_url_prefix`, and `gc_api_base_url` are injected by gc when the adapter runs as a `proxy_process` service. ## upgrading to a larger tier to gain channel bindings, per-session identity, or multi-rig routing, swap this pack for `slack-channel` or `slack-full`. the bot token, signing secret, and workspace id carry over unchanged. see the tiering memo's migration section. 0.1.0 initial slack mini pack release with canonical content hash.", + "search_text": "slack-mini gascity-packs maintained gas city minimal slack mention bridge and outbound message pack for gas city. https://github.com/gastownhall/gascity-packs/tree/main/slack-mini git # slack-mini **talk to your gas city mayor from slack.** add a bot to your workspace, `@`-mention it from any channel, and the mention bridges to your gc mayor session. reply back with one verb. slack-mini is **tier 1** of the slack pack family — the smallest surface that gets a human talking to gc over slack. it ships a small two-file adapter and one outbound verb. no channel bindings, no per-session identity, no multi-rig routing — those live in `slack-channel` (tier 2) and `slack-full` (tier 3). see the [slack-pack tiering design memo](../docs/design/slack-pack-tiering.md) (landing separately). > pick exactly one slack tier per city. the tiers are alternatives, not > layers you stack. ## what you get - **inbound:** `@gc-mayor what's the convoy status?` in any channel the bot is in → delivered to your gc mayor session. - **outbound:** `gc slack-mini post-message --channel c0123 --text \"…\"` → posts to a channel, optionally in a thread. ## choose a transport inbound mentions reach the adapter one of two ways. pick one before you create the app — the choice is baked into the app manifest. | | **socket mode** | **http (events api)** | | --- | --- | --- | | public ingress | none | public https url required | | how events arrive | outbound websocket the adapter opens to slack | slack posts to your request url | | credentials | app-level token (`xapp-…`) | signing secret | | manifest | [`manifest/app-socket.json`](./manifest/app-socket.json) | [`manifest/app.json`](./manifest/app.json) | **socket mode is the right default if you cannot expose an inbound port** — behind a corporate firewall, on a laptop, or anywhere a tunnel is blocked. it needs only outbound https to `slack.com`. choose http when you already have public ingress and would rather not manage another token. everything downstream is identical: same mention handling, same bridge into gc, same outbound verb. ## install in 3 minutes ### 1. create the slack app (1 min) create an app from the shipped manifest — `manifest/app-socket.json` for socket mode, `manifest/app.json` for http: 1. → **create new app** → **from a manifest**. 2. pick your workspace, paste the manifest, create. 3. **install to workspace** and copy the **bot user oauth token** (`xoxb-…`). 4. from **basic information**: - **socket mode:** under **app-level tokens**, **generate token and scopes**, add the **`connections:write`** scope, and copy the token (`xapp-…`). app-level tokens cannot be declared in a manifest, so this step is manual. - **http:** copy the **signing secret**. both manifests request only three bot scopes — `app_mentions:read`, `chat:write`, `chat:write.public` — and subscribe to the `app_mention` event. they differ in `socket_mode_enabled` and in the display description text; nothing else. ### 2. configure the city (1 min) import the pack into your city: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//slack-mini ``` that writes the import and pins the release in `packs.lock`. the same import by hand in `pack.toml`, followed by `gc import install`: ```toml [imports.slack-mini] source = \"https://github.com/gastownhall/gascity-packs.git//slack-mini\" ``` `gc pack registry show slack-mini` prints the registry entry and its releases. to work on the pack from a local checkout, point `source` at the path instead: ```toml [imports.slack-mini] source = \"../gascity-packs/slack-mini\" ``` provide the adapter's environment (e.g. in your city service env or `~/.config/gc-slack-mini-adapter/env`): **socket mode:** ```sh slack_bot_token=xoxb-... # from step 1 slack_app_token=xapp-... # from step 1 — selects socket mode slack_workspace_id=t0123abcd # your slack team id gc_city_name= # the gc city to bridge into ``` **http:** ```sh slack_bot_token=xoxb-... # from step 1 slack_signing_secret=... # from step 1 slack_workspace_id=t0123abcd # your slack team id gc_city_name= # the gc city to bridge into ``` that's the full required set either way — four variables. setting `slack_app_token` is the whole switch: the adapter runs socket mode, never binds `listen_public`, and stops requiring `slack_signing_secret` (socket mode has no request signatures to verify). ### 3. start (1 min) **socket mode — nothing to expose.** the adapter dials out to slack, so there is no listener to publish, no tls to terminate, and no request url to register. start your city and `@`-mention the bot. the log line to look for is `socket mode: connected`. **http — expose the events endpoint.** the adapter listens for slack events on public tcp (`listen_public`, default `0.0.0.0:8775`). terminate tls in front of it and give slack a public url — [tailscale funnel](https://tailscale.com/kb/1223/funnel) is the easy path: ```sh tailscale funnel 8775 ``` then in the slack app's **event subscriptions**, set the request url to `https:///slack/events`. slack sends a one-time `url_verification` challenge, which the adapter answers automatically. gc supervises the adapter as a `proxy_process` service (named `slack-mini`); the service command is `adapter/run.sh`, which builds the binary from source on first start (see [build](#build)). start your city and `@`-mention the bot. ## replying in a thread when the mayor session handles an inbound mention, the conversation carries the slack message `ts` as its reply-to id. answer in the same thread with: ```sh gc slack-mini post-message --channel c0123 \\ --thread-ts 1700000000.0001 --text \"on it — see pr #42\" ``` ## build the adapter's only dependency is [`github.com/coder/websocket`](https://github.com/coder/websocket) (itself dependency-free), used for the socket mode transport: ```sh cd adapter go build -o gc-slack-mini-adapter ./... ``` the built binary is git-ignored. the `[[service]]` block runs `adapter/run.sh`, which execs the binary when it exists and otherwise builds it from the sources next to it first, so a pack pin bump (`gc import install` re-materializes the pack from git only) no longer strands the service. the first build runs inside gc's readiness window, so expect one or more restarts until the go build cache is warm; a go toolchain must be installed. ## configuration reference | variable | required | default | purpose | | --- | :---: | --- | --- | | `slack_bot_token` | ✓ | — | bot token for `chat.postmessage` and the inbound post. | | `slack_signing_secret` | http only | — | hmac secret for verifying slack requests. required unless `slack_app_token` is set. | | `slack_workspace_id` | ✓ | — | slack team id (the extmsg account id). also gates inbound: events carrying a different `team_id` are dropped on both transports, so a wrong value looks like a silent bot. | | `gc_city_name` | ✓ | — | gc city to bridge into. | | `slack_app_token` | socket mode | — | app-level token (`xapp-…`) with `connections:write`. setting it selects socket mode: no public listener, no signing secret. | | `listen_public` | | `0.0.0.0:8775` | public bind for `/slack/events`. not bound in socket mode. | | `listen_internal` | | `127.0.0.1:8776` | tcp bind for `/post-message` when not run as a gc proxy_process (no `gc_service_socket`). | | `register_on_start` | | `true` | self-register as an extmsg adapter on start. | | `slack_mini_inbound_target` | | `mayor` | session handle inbound mentions address. | | `slack_api_base` | | `https://slack.com/api` | slack web api origin (override for relays/tests). | | `gc_api_base_url` | | `http://127.0.0.1:9443` | gc api base. | `gc_service_socket`, `gc_service_url_prefix`, and `gc_api_base_url` are injected by gc when the adapter runs as a `proxy_process` service. ## socket mode notes - **egress only.** the adapter needs outbound https to `slack.com` and a websocket to `wss-*.slack.com`. nothing inbound. - **proxies are honoured.** the connection is dialled through go's default http client, so `https_proxy` / `http_proxy` / `no_proxy` apply — useful on networks that force egress through a proxy. - **reconnects are normal.** slack recycles connections routinely and warns ~10s ahead; the adapter opens a replacement while the old connection drains, so mentions are not dropped. every redial is paced, from 1s up to a 30s cap — a 1s pause fits well inside the warning's lead, so an ordinary refresh still overlaps. the pacing only escalates for connections that die before lasting 30s, which is what stops a server that accepts and immediately drops connections from producing a redial loop. - **redelivery is safe.** every envelope is acked before bridging, and each bridged message carries a `dedup_key` of `slack-`, so a redelivered event does not produce a duplicate in gc. - **`invalid_auth` on start** almost always means the app-level token is missing the `connections:write` scope, or a bot token was pasted into `slack_app_token`. ## upgrading to a larger tier to gain channel bindings, per-session identity, or multi-rig routing, swap this pack for `slack-channel` or `slack-full`. the bot token, signing secret, and workspace id carry over unchanged — but note that socket mode is currently implemented at tier 1 only, so the larger tiers still need public ingress. see the tiering memo's migration section. 0.1.0 initial slack mini pack release with canonical content hash. 0.1.1 socket mode transport for ingress-free installs, a checked-in run.sh that rebuilds a missing adapter binary, and install docs for the git import.", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/slack-mini/README.md", - "content": "# slack-mini\n\n**Talk to your Gas City mayor from Slack.** Add a bot to your workspace,\n`@`-mention it from any channel, and the mention bridges to your gc mayor\nsession. Reply back with one verb.\n\nslack-mini is **Tier 1** of the Slack pack family — the smallest surface\nthat gets a human talking to gc over Slack. It ships a single-file adapter\nand one outbound verb. No channel bindings, no per-session identity, no\nmulti-rig routing — those live in `slack-channel` (Tier 2) and `slack-full`\n(Tier 3). See the [slack-pack tiering design memo](../docs/design/slack-pack-tiering.md)\n(landing separately).\n\n> Pick exactly one Slack tier per city. The tiers are alternatives, not\n> layers you stack.\n\n## What you get\n\n- **Inbound:** `@gc-mayor what's the convoy status?` in any channel the bot\n is in → delivered to your gc mayor session.\n- **Outbound:** `gc slack-mini post-message --channel C0123 --text \"…\"` →\n posts to a channel, optionally in a thread.\n\n## Install in 3 minutes\n\n### 1. Create the Slack app (1 min)\n\nCreate an app from the shipped manifest at\n[`manifest/app.json`](./manifest/app.json):\n\n1. → **Create New App** → **From a manifest**.\n2. Pick your workspace, paste `manifest/app.json`, create.\n3. **Install to Workspace** and copy the **Bot User OAuth Token**\n (`xoxb-…`).\n4. From **Basic Information**, copy the **Signing Secret**.\n\nThe manifest requests only three scopes — `app_mentions:read`,\n`chat:write`, `chat:write.public` — and subscribes to the `app_mention`\nevent.\n\n### 2. Configure the city (1 min)\n\nImport the pack in your city's `pack.toml`:\n\n```toml\n[imports.slack-mini]\nsource = \"../packs/slack-mini\"\n```\n\nProvide the adapter's environment (e.g. in your city service env or\n`~/.config/gc-slack-mini-adapter/env`):\n\n```sh\nSLACK_BOT_TOKEN=xoxb-... # from step 1\nSLACK_SIGNING_SECRET=... # from step 1\nSLACK_WORKSPACE_ID=T0123ABCD # your Slack team id\nGC_CITY_NAME= # the gc city to bridge into\n```\n\nThat's the full required set — four variables.\n\n### 3. Expose the events endpoint and start (1 min)\n\nThe adapter listens for Slack events on public TCP (`LISTEN_PUBLIC`,\ndefault `0.0.0.0:8775`). Terminate TLS in front of it and give Slack a\npublic URL — [Tailscale Funnel](https://tailscale.com/kb/1223/funnel) is\nthe easy path:\n\n```sh\ntailscale funnel 8775\n```\n\nThen in the Slack app's **Event Subscriptions**, set the Request URL to\n`https:///slack/events`. Slack sends a one-time\n`url_verification` challenge, which the adapter answers automatically.\n\ngc supervises the adapter as a `proxy_process` service (named\n`slack-mini`); building the binary is a one-time `go build` in `adapter/`\n(see [Build](#build)). Start your city and `@`-mention the bot.\n\n## Replying in a thread\n\nWhen the mayor session handles an inbound mention, the conversation carries\nthe Slack message `ts` as its reply-to id. Answer in the same thread with:\n\n```sh\ngc slack-mini post-message --channel C0123 \\\n --thread-ts 1700000000.0001 --text \"on it — see PR #42\"\n```\n\n## Build\n\nThe adapter is a single Go file with no external dependencies:\n\n```sh\ncd adapter\ngo build -o gc-slack-mini-adapter ./...\n```\n\nThe built binary is git-ignored; the `[[service]]` block runs it in place.\n\n## Configuration reference\n\n| Variable | Required | Default | Purpose |\n| --- | :---: | --- | --- |\n| `SLACK_BOT_TOKEN` | ✓ | — | Bot token for `chat.postMessage` and the inbound POST. |\n| `SLACK_SIGNING_SECRET` | ✓ | — | HMAC secret for verifying Slack requests. |\n| `SLACK_WORKSPACE_ID` | ✓ | — | Slack team id (the extmsg account id). |\n| `GC_CITY_NAME` | ✓ | — | gc city to bridge into. |\n| `LISTEN_PUBLIC` | | `0.0.0.0:8775` | Public bind for `/slack/events`. |\n| `LISTEN_INTERNAL` | | `127.0.0.1:8776` | TCP bind for `/post-message` when not run as a gc proxy_process (no `GC_SERVICE_SOCKET`). |\n| `REGISTER_ON_START` | | `true` | Self-register as an extmsg adapter on start. |\n| `SLACK_MINI_INBOUND_TARGET` | | `mayor` | Session handle inbound mentions address. |\n| `SLACK_API_BASE` | | `https://slack.com/api` | Slack web API origin (override for relays/tests). |\n| `GC_API_BASE_URL` | | `http://127.0.0.1:9443` | gc API base. |\n\n`GC_SERVICE_SOCKET`, `GC_SERVICE_URL_PREFIX`, and `GC_API_BASE_URL` are\ninjected by gc when the adapter runs as a `proxy_process` service.\n\n## Upgrading to a larger tier\n\nTo gain channel bindings, per-session identity, or multi-rig routing, swap\nthis pack for `slack-channel` or `slack-full`. The bot token, signing\nsecret, and workspace id carry over unchanged. See the tiering memo's\nmigration section." + "content": "# slack-mini\n\n**Talk to your Gas City mayor from Slack.** Add a bot to your workspace,\n`@`-mention it from any channel, and the mention bridges to your gc mayor\nsession. Reply back with one verb.\n\nslack-mini is **Tier 1** of the Slack pack family — the smallest surface\nthat gets a human talking to gc over Slack. It ships a small two-file\nadapter and one outbound verb. No channel bindings, no per-session identity, no\nmulti-rig routing — those live in `slack-channel` (Tier 2) and `slack-full`\n(Tier 3). See the [slack-pack tiering design memo](../docs/design/slack-pack-tiering.md)\n(landing separately).\n\n> Pick exactly one Slack tier per city. The tiers are alternatives, not\n> layers you stack.\n\n## What you get\n\n- **Inbound:** `@gc-mayor what's the convoy status?` in any channel the bot\n is in → delivered to your gc mayor session.\n- **Outbound:** `gc slack-mini post-message --channel C0123 --text \"…\"` →\n posts to a channel, optionally in a thread.\n\n## Choose a transport\n\nInbound mentions reach the adapter one of two ways. Pick one before you\ncreate the app — the choice is baked into the app manifest.\n\n| | **Socket Mode** | **HTTP (Events API)** |\n| --- | --- | --- |\n| Public ingress | none | public HTTPS URL required |\n| How events arrive | outbound WebSocket the adapter opens to Slack | Slack POSTs to your Request URL |\n| Credentials | app-level token (`xapp-…`) | signing secret |\n| Manifest | [`manifest/app-socket.json`](./manifest/app-socket.json) | [`manifest/app.json`](./manifest/app.json) |\n\n**Socket Mode is the right default if you cannot expose an inbound port** —\nbehind a corporate firewall, on a laptop, or anywhere a tunnel is blocked.\nIt needs only outbound HTTPS to `slack.com`. Choose HTTP when you already\nhave public ingress and would rather not manage another token.\n\nEverything downstream is identical: same mention handling, same bridge into\ngc, same outbound verb.\n\n## Install in 3 minutes\n\n### 1. Create the Slack app (1 min)\n\nCreate an app from the shipped manifest — `manifest/app-socket.json` for\nSocket Mode, `manifest/app.json` for HTTP:\n\n1. → **Create New App** → **From a manifest**.\n2. Pick your workspace, paste the manifest, create.\n3. **Install to Workspace** and copy the **Bot User OAuth Token**\n (`xoxb-…`).\n4. From **Basic Information**:\n - **Socket Mode:** under **App-Level Tokens**, **Generate Token and\n Scopes**, add the **`connections:write`** scope, and copy the token\n (`xapp-…`). App-level tokens cannot be declared in a manifest, so this\n step is manual.\n - **HTTP:** copy the **Signing Secret**.\n\nBoth manifests request only three bot scopes — `app_mentions:read`,\n`chat:write`, `chat:write.public` — and subscribe to the `app_mention`\nevent. They differ in `socket_mode_enabled` and in the display description\ntext; nothing else.\n\n### 2. Configure the city (1 min)\n\nImport the pack into your city:\n\n```sh\ngc import add https://github.com/gastownhall/gascity-packs.git//slack-mini\n```\n\nThat writes the import and pins the release in `packs.lock`. The same import\nby hand in `pack.toml`, followed by `gc import install`:\n\n```toml\n[imports.slack-mini]\nsource = \"https://github.com/gastownhall/gascity-packs.git//slack-mini\"\n```\n\n`gc pack registry show slack-mini` prints the registry entry and its releases.\nTo work on the pack from a local checkout, point `source` at the path\ninstead:\n\n```toml\n[imports.slack-mini]\nsource = \"../gascity-packs/slack-mini\"\n```\n\nProvide the adapter's environment (e.g. in your city service env or\n`~/.config/gc-slack-mini-adapter/env`):\n\n**Socket Mode:**\n\n```sh\nSLACK_BOT_TOKEN=xoxb-... # from step 1\nSLACK_APP_TOKEN=xapp-... # from step 1 — selects Socket Mode\nSLACK_WORKSPACE_ID=T0123ABCD # your Slack team id\nGC_CITY_NAME= # the gc city to bridge into\n```\n\n**HTTP:**\n\n```sh\nSLACK_BOT_TOKEN=xoxb-... # from step 1\nSLACK_SIGNING_SECRET=... # from step 1\nSLACK_WORKSPACE_ID=T0123ABCD # your Slack team id\nGC_CITY_NAME= # the gc city to bridge into\n```\n\nThat's the full required set either way — four variables. Setting\n`SLACK_APP_TOKEN` is the whole switch: the adapter runs Socket Mode, never\nbinds `LISTEN_PUBLIC`, and stops requiring `SLACK_SIGNING_SECRET` (Socket\nMode has no request signatures to verify).\n\n### 3. Start (1 min)\n\n**Socket Mode — nothing to expose.** The adapter dials out to Slack, so\nthere is no listener to publish, no TLS to terminate, and no Request URL to\nregister. Start your city and `@`-mention the bot. The log line to look for\nis `socket mode: connected`.\n\n**HTTP — expose the events endpoint.** The adapter listens for Slack events\non public TCP (`LISTEN_PUBLIC`, default `0.0.0.0:8775`). Terminate TLS in\nfront of it and give Slack a public URL —\n[Tailscale Funnel](https://tailscale.com/kb/1223/funnel) is the easy path:\n\n```sh\ntailscale funnel 8775\n```\n\nThen in the Slack app's **Event Subscriptions**, set the Request URL to\n`https:///slack/events`. Slack sends a one-time\n`url_verification` challenge, which the adapter answers automatically.\n\ngc supervises the adapter as a `proxy_process` service (named\n`slack-mini`); the service command is `adapter/run.sh`, which builds the\nbinary from source on first start (see [Build](#build)). Start your city and `@`-mention the bot.\n\n## Replying in a thread\n\nWhen the mayor session handles an inbound mention, the conversation carries\nthe Slack message `ts` as its reply-to id. Answer in the same thread with:\n\n```sh\ngc slack-mini post-message --channel C0123 \\\n --thread-ts 1700000000.0001 --text \"on it — see PR #42\"\n```\n\n## Build\n\nThe adapter's only dependency is\n[`github.com/coder/websocket`](https://github.com/coder/websocket) (itself\ndependency-free), used for the Socket Mode transport:\n\n```sh\ncd adapter\ngo build -o gc-slack-mini-adapter ./...\n```\n\nThe built binary is git-ignored. The `[[service]]` block runs `adapter/run.sh`,\nwhich execs the binary when it exists and otherwise builds it from the\nsources next to it first, so a pack pin bump (`gc import install`\nre-materializes the pack from git only) no longer strands the service. The\nfirst build runs inside gc's readiness window, so expect one or more\nrestarts until the Go build cache is warm; a Go toolchain must be installed.\n\n## Configuration reference\n\n| Variable | Required | Default | Purpose |\n| --- | :---: | --- | --- |\n| `SLACK_BOT_TOKEN` | ✓ | — | Bot token for `chat.postMessage` and the inbound POST. |\n| `SLACK_SIGNING_SECRET` | HTTP only | — | HMAC secret for verifying Slack requests. Required unless `SLACK_APP_TOKEN` is set. |\n| `SLACK_WORKSPACE_ID` | ✓ | — | Slack team id (the extmsg account id). Also gates inbound: events carrying a different `team_id` are dropped on both transports, so a wrong value looks like a silent bot. |\n| `GC_CITY_NAME` | ✓ | — | gc city to bridge into. |\n| `SLACK_APP_TOKEN` | Socket Mode | — | App-level token (`xapp-…`) with `connections:write`. Setting it selects Socket Mode: no public listener, no signing secret. |\n| `LISTEN_PUBLIC` | | `0.0.0.0:8775` | Public bind for `/slack/events`. Not bound in Socket Mode. |\n| `LISTEN_INTERNAL` | | `127.0.0.1:8776` | TCP bind for `/post-message` when not run as a gc proxy_process (no `GC_SERVICE_SOCKET`). |\n| `REGISTER_ON_START` | | `true` | Self-register as an extmsg adapter on start. |\n| `SLACK_MINI_INBOUND_TARGET` | | `mayor` | Session handle inbound mentions address. |\n| `SLACK_API_BASE` | | `https://slack.com/api` | Slack web API origin (override for relays/tests). |\n| `GC_API_BASE_URL` | | `http://127.0.0.1:9443` | gc API base. |\n\n`GC_SERVICE_SOCKET`, `GC_SERVICE_URL_PREFIX`, and `GC_API_BASE_URL` are\ninjected by gc when the adapter runs as a `proxy_process` service.\n\n## Socket Mode notes\n\n- **Egress only.** The adapter needs outbound HTTPS to `slack.com` and a\n WebSocket to `wss-*.slack.com`. Nothing inbound.\n- **Proxies are honoured.** The connection is dialled through Go's default\n HTTP client, so `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` apply — useful\n on networks that force egress through a proxy.\n- **Reconnects are normal.** Slack recycles connections routinely and warns\n ~10s ahead; the adapter opens a replacement while the old connection\n drains, so mentions are not dropped. Every redial is paced, from 1s up to\n a 30s cap — a 1s pause fits well inside the warning's lead, so an ordinary\n refresh still overlaps. The pacing only escalates for connections that die\n before lasting 30s, which is what stops a server that accepts and\n immediately drops connections from producing a redial loop.\n- **Redelivery is safe.** Every envelope is acked before bridging, and each\n bridged message carries a `dedup_key` of `slack-`, so a redelivered\n event does not produce a duplicate in gc.\n- **`invalid_auth` on start** almost always means the app-level token is\n missing the `connections:write` scope, or a bot token was pasted into\n `SLACK_APP_TOKEN`.\n\n## Upgrading to a larger tier\n\nTo gain channel bindings, per-session identity, or multi-rig routing, swap\nthis pack for `slack-channel` or `slack-full`. The bot token, signing\nsecret, and workspace id carry over unchanged — but note that Socket Mode\nis currently implemented at Tier 1 only, so the larger tiers still need\npublic ingress. See the tiering memo's\nmigration section." }, "og_image": "/og/slack-mini.svg", - "latest": "0.1.0", + "latest": "0.1.1", "releases": [ { "version": "0.1.0", @@ -555,6 +587,14 @@ "hash": "sha256:fff2523a9ab32e0812815c8d2addb08e6349d3885052d095bd76600eec87921a", "description": "Initial Slack mini pack release with canonical content hash.", "withdrawn": false + }, + { + "version": "0.1.1", + "ref": "main", + "commit": "ea4066504ef753c0b4e8bcc73aea2d447c964969", + "hash": "sha256:59996bf0c9dcdedf72a3f058cb13cfb421042fc4099d7beb5d03900523800324", + "description": "Socket Mode transport for ingress-free installs, a checked-in run.sh that rebuilds a missing adapter binary, and install docs for the git import.", + "withdrawn": false } ] }, @@ -567,13 +607,13 @@ "description": "obra/superpowers TDD-first skill library as a Gas City build factory.", "source": "https://github.com/gastownhall/gascity-packs/tree/main/superpowers", "source_kind": "git", - "search_text": "superpowers gascity-packs maintained gas city obra/superpowers tdd-first skill library as a gas city build factory. https://github.com/gastownhall/gascity-packs/tree/main/superpowers git # superpowers pack this pack runs [superpowers](https://github.com/obra/superpowers), jesse vincent's skill library for disciplined agent development, as a gas city build factory. the superpowers process is: brainstorm a design, get the design and a written spec approved, implement each task with strict test-driven development, then review every task twice (spec compliance first, code quality second) before a final fan-out code review. the pack vendors the upstream skills and maps that process onto gas city's `build-base` workflow contract as the `superpowers-build` formula, so one `gc sling` command runs the whole pipeline. ## when to choose superpowers - you want hard approval gates before any code is written. the requirements stage is two explicit loops — design approval, then written-spec approval — and a failed gate sends the work back through the same loop instead of falling through to implementation. - you want strict tdd per task. every implementation bead runs a durable write-failing-test → verify-it-fails → implement → verify-it-passes sequence as graph steps, not as prose guidance an agent can skip. - you want two-stage review of every task: a spec-compliance pass and a code-quality pass per task, plus a post-implementation code-review and gap-analysis fanout over the whole change. - compared with the siblings: `build-basic` (in the [gascity](../gascity) pack) is the default factory with the fewest gates; [compound-engineering](../compound-engineering) trades approval gates for the widest reviewer-persona fanout; [bmad](../bmad) is document-first (prd/architecture/stories); [gstack](../gstack) adds founder/pm-flavored qa and release-readiness stages. pick superpowers when up-front spec rigor and per-task tdd matter most. ## quick start the goal: from a fresh gas city install to a completed `superpowers-build` run. 1. **install gas city and start a city** (skip what you already have): ```sh brew install gascity gc init ~/my-city cd ~/my-city gc start ``` 2. **add the repository you want agents to work on as a rig:** ```sh git clone https://github.com/you/your-project cd your-project gc rig add . ``` 3. **import the pack.** from the city directory, add `superpowers` at city scope. this writes the import, fetches the latest release, and pins it in `packs.lock` — no clone needed. the pack imports the gas city pack internally as `gc`, so `build-base` and the shared `gc.*` formula surface come along transitively: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//superpowers ``` 4. **import the rig roles** in your city's `city.toml`. each rig that should run work also needs the `gascity/roles` import, which provides the worker agents (`gc.run-operator`, `gc.task-decomposer`, and friends) that formula steps route to; run `gc import install` after editing: ```toml [[rigs]] name = \"your-project\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/superpowers`) instead. 5. **run your first build.** `superpowers-build` is a targeted formula (`target_required = true`): create a bead describing the goal, then sling the formula against it. `artifact_root` is the only required variable: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` 6. **what happens next.** the run walks prepare → brainstorm + spec approval → plan → plan review → decomposition into a task convoy → per-task tdd implementation → code-review/gap-analysis fanout → finalize → publish. stage artifacts (the approved spec, the plan, the decomposition, review reports, and the final report) land under `artifact_root` inside your rig. the publish stage only pushes or opens a pr when you opt in with `push=true` / `open_pr=true`. 7. **watch progress.** list the workflow's beads with `gc bd --rig your-project` and the implementation convoy with `gc convoy --rig your-project`. to inspect the formula surface itself, use `gc formula catalog --json` and `gc formula show superpowers-build --json`. ## stage map `superpowers-build` extends `build-base` and keeps the inherited anchor order `prepare -> requirements -> plan -> plan-review -> decompose -> implement/implement-same-session -> review -> finalize -> publish`. no anchor is renamed, skipped, or reordered, and no top-level stage is added. | `build-base` anchor | `superpowers-build` behavior | | ------------------- | ---------------------------- | | `prepare` | inherited from `build-base`. | | `requirements` | brainstorm + written-spec approval loops (`superpowers-brainstorming` expansion). | | `plan` | plan written with the vendored `writing-plans` skill. | | `plan-review` | plan review loop (`superpowers-plan-review` expansion). | | `decompose` | scope-only task beads + implementation convoy. | | `implement` / `implement-same-session` | tdd development drains (`superpowers-development` / `superpowers-development-item`). | | `review` | code-review + gap-analysis fanout (`superpowers-code-review` expansion). | | `finalize` | vendored `finishing-a-development-branch` skill. | | `publish` | inherited from `build-base`; gated by `push` / `open_pr`. | the native stage formulas extend the matching base methodology contracts: `superpowers-planning` (`planning-base`), `superpowers-decomposition` (`decomposition-base`), `superpowers-implementation` (`implement`), `superpowers-review` (`code-review-base`), and `superpowers-fix-loop` (`fix-loop-base`). `superpowers-build` pins them as its selector defaults (see the customization table) with `implementation_target` defaulting to `superpowers.implementer`. supported modes and drain policies, as declared in `[metadata.gc.methodology]`: - `interaction_modes`: `interactive`, `autonomous`, `headless` (inherited `interaction_mode` var, default `interactive`) - `review_modes`: `report`, `agent`, `interactive` (inherited `review_mode` var, default `agent`) - `implementation_strategy`: `drain` with `allowed_drain_policies` of `separate` (drains `superpowers-development` with exclusive member access) and `same-session` (drains `superpowers-development-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`) ### per-task tdd and task review inside both implementation item formulas, the red/green discipline from the vendored `test-driven-development` skill is durable graph structure: each drained task chains `write-failing-test -> verify-test-fails -> implement-change -> verify-test-passes -> task-review -> record-item-result` as formula steps with explicit `needs` edges, so every task's tdd pass is resumable and visible in the graph. superpowers task review is the per-item conversion point for raw `subagent-driven-development`. the `superpowers-task-review` expansion adds spec-compliance and code-quality fanout lanes after each task's tdd pass. those lanes preserve the expected review order without launching provider-native subagents: the spec-compliance lane runs first, the implementation lane applies spec findings, the code-quality lane reviews the corrected task, and the final apply lane owns the `code_review.verdict=done|iterate` loop. ### review/fix loop the review/fix loop is graph structure: the `review` anchor expands `superpowers-code-review`, which fans out sibling `request-code-review` and `gap-analysis-review` lanes, fans in at `process-code-review` (routed to the caller-selected implementation target), and loops through a bounded graph check until the `code_review.verdict=done` approval lands on the workflow root. `superpowers-fix-loop` carries the same review-fix contract for standalone adapter use. ### end-to-end flow ```mermaid flowchart td classdef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef superpowers fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch superpowers-build\"]:::terminal --> prepare[\"prepare
inherited build-base\"]:::inherited prepare --> reqsetup subgraph requirements[\"requirements override: superpowers brainstorming + written-spec loops\"] direction tb reqsetup[\"setup brainstorming context
superpowers override\"]:::superpowers --> brainstormdesign[\"brainstorm-design
brainstorming skill\"]:::superpowers brainstormdesign --> confirmdesign[\"confirm-design-approval
superpowers approval pass\"]:::superpowers confirmdesign --> designgate{\"design approved?
graph check\"}:::infra designgate -->|no: loop with feedback| brainstormdesign designgate -->|yes| writespec[\"write-requirements-spec
superpowers spec pass\"]:::superpowers writespec --> specreview[\"review-written-spec
spec reviewer prompt\"]:::superpowers specreview --> applyspecfeedback[\"apply-spec-feedback
brainstorming lane\"]:::superpowers applyspecfeedback --> confirmspec[\"confirm-spec-approval
superpowers approval pass\"]:::superpowers confirmspec --> specgate{\"spec approved?
graph check\"}:::infra specgate -->|no: next spec iteration| writespec specgate -->|yes| requirementsdone[\"finalize approved requirements
superpowers override\"]:::superpowers end requirementsdone --> planwrite[\"plan
writing-plans skill override\"]:::superpowers subgraph planreview[\"plan-review override: superpowers plan review loop\"] direction tb planwrite --> planreviewsetup[\"prepare plan review context
superpowers override\"]:::superpowers planreviewsetup --> plandocreview[\"plan-document-review
plan reviewer prompt\"]:::superpowers plandocreview --> applyplanfeedback[\"apply-plan-feedback
writing-plans lane\"]:::superpowers applyplanfeedback --> plangate{\"plan approved?
graph check\"}:::infra plangate -->|no: review revised plan| plandocreview plangate -->|yes| planreviewdone[\"finalize approved plan
superpowers override\"]:::superpowers end planreviewdone --> decompose[\"decompose
superpowers override: scope-only task beads + convoy\"]:::superpowers decompose --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra drainseparate --> itemfanout[\"fan out runnable implementation beads\"]:::infra drainshared --> itemserial[\"serialize convoy members\"]:::infra subgraph itemformula[\"implementation item formulas: one formula instance per implementation bead\"] direction tb separateprep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> separateinspect[\"inspect task scope
superpowers implement override\"]:::superpowers sharedinspect[\"inspect shared task scope
superpowers implement-item override\"]:::superpowers separateinspect --> writetest[\"write failing test\"]:::superpowers sharedinspect --> writetest writetest --> verifyred[\"verify test fails\"]:::superpowers verifyred --> code[\"implement minimal change\"]:::superpowers code --> verifygreen[\"verify tests pass\"]:::superpowers verifygreen --> taskreview[\"task-review
spec + quality fanout\"]:::superpowers taskreview --> record[\"record task result\"]:::superpowers record --> closeitem[\"close source anchor
superpowers override\"]:::superpowers end itemfanout --> separateprep itemserial --> sharedinspect closeitem --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra drainfanin --> reviewsetup[\"review
superpowers code-review override\"]:::superpowers subgraph reviewloop[\"post-implementation review loop\"] direction tb reviewsetup --> reviewfanout[\"fan out review lanes\"]:::infra subgraph reviewlanes[\"parallel reviewer lanes: sibling beads with no needs between them\"] direction lr codereview[\"request-code-review lane\"]:::superpowers gapreview[\"gap-analysis-review lane\"]:::superpowers end reviewfanout --> codereview reviewfanout --> gapreview codereview --> reviewfanin[\"fan in
wait for both review artifacts\"]:::infra gapreview --> reviewfanin reviewfanin --> reviewprocess[\"process code-review feedback\"]:::superpowers reviewprocess --> reviewdecision{\"implementation approved?
graph check\"}:::infra reviewdecision -->|no: fix and re-review| reviewfanout reviewdecision -->|yes| reviewdone[\"review approved\"]:::superpowers end reviewdone --> finalize[\"finalize
finishing-a-development-branch override\"]:::superpowers finalize --> publish[\"publish
inherited build-base\"]:::inherited publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are inherited base behavior, green nodes are superpowers-specific overrides, and amber nodes are gas city graph, convoy, or drain infrastructure. the brainstorm and plan phases are explicit approval loops: a failed graph check creates another iteration of the same loop rather than falling through to implementation. the post-implementation review lanes are real fan-out/fan-in graph work — the reviewer beads are siblings with no dependencies between them, and only the feedback-processing bead waits on both review artifacts. in the separate-drain path, independent implementation beads can run in parallel subject to convoy dependencies, each in its own item formula instance with session affinity scoped to that bead. in the same-session path, the drain serializes convoy members through one shared single-lane session. both paths converge before the post-implementation review fan-out. ## customization all variables are passed at launch with `--var name=value`. the first group is inherited from `build-base`; `brainstorming_approval_mode` is superpowers-specific. | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | required | directory inside the rig where all stage artifacts are written. | | `interaction_mode` | `interactive` | human participation posture for the whole workflow: `interactive`, `autonomous`, or `headless`. | | `brainstorming_approval_mode` | `autonomous` | the stock superpowers \"user approves design?\" gate. `interactive` makes the design and spec approval passes wait on a human gate; `autonomous` drives the same loops without user checkpoints. | | `review_mode` | `agent` | review authority: `report` synthesizes findings without applying fixes; `agent` and `interactive` feed the review-fix lane until the approval check passes. | | `drain_policy` | `separate` | `separate` drains each task in its own worktree and session, in parallel where convoy dependencies allow; `same-session` serializes all tasks through one shared session. | | `implementation_target` | `superpowers.implementer` | role that runs implementation and apply-fix lanes. | | `push` | `false` | set `true` to allow the publish stage to push after all checks pass. | | `open_pr` | `false` | set `true` to allow the publish stage to open a pr after all checks pass. | | `max_iterations` | `10` | bound on implementation/review fix attempts. | | `context_path` | `\"\"` | optional source context bundle path. | | `requirements_path`, `plan_path`, `decomposition_path` | `\"\"` | reuse an existing artifact instead of regenerating it. | | `planning_formula` | `superpowers-planning` | selector: planning methodology formula. | | `decomposition_formula` | `superpowers-decomposition` | selector: decomposition methodology formula. | | `implementation_formula` | `superpowers-implementation` | selector: implementation entry formula. | | `implementation_item_formula` | `superpowers-development-item` | selector: single-item implementation formula for shared drains. | | `code_review_formula` | `superpowers-review` | selector: code-review methodology formula. | | `review_fix_formula` | `superpowers-fix-loop` | selector: review-fix methodology formula. | to change what a stage *says* without changing the graph, shadow its prompt asset. step bodies live at `assets/workflows//.md` and resolve through the normal import/layer search path. for example, to add project-specific planning rules, create `assets/workflows/superpowers-build/plan.md` in your city's asset layer; it replaces this pack's `superpowers/assets/workflows/superpowers-build/plan.md` without touching the formula. for the full contract — including advanced step overrides and the artifact invariants they must preserve — see \"stable workflow override interface\" in the [gascity readme](../gascity/readme.md). ## examples interactive first feature build with human spec approval. `interaction_mode` defaults to `interactive`; setting `brainstorming_approval_mode=interactive` additionally makes the design and written-spec gates wait for your approval before any code is planned or written: ```sh gc bd create \"add rate limiting to the public api\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/rate-limiting/build \\ --var brainstorming_approval_mode=interactive ``` autonomous run that pushes and opens a pr. no human checkpoints: the approval loops, tdd tasks, and review fanout all run unattended, and publish is allowed to push the branch and open a pr once every check passes: ```sh gc bd create \"migrate config loading from json to toml\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/toml-config/build \\ --var interaction_mode=autonomous \\ --var push=true \\ --var open_pr=true ``` same-session drain for a small change. all tasks run serially in one shared worktree and conversation instead of parallel per-task worktrees — useful when the change is small and continuity matters more than parallelism: ```sh gc bd create \"fix off-by-one in pagination cursor\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/pagination-fix/build \\ --var drain_policy=same-session ``` ## what's vendored - formula: `superpowers-build` - methodology formulas: `superpowers-planning`, `superpowers-decomposition`, `superpowers-implementation`, `superpowers-review`, and `superpowers-fix-loop` - expansion formulas: `superpowers-brainstorming`, `superpowers-plan-review`, `superpowers-task-review`, and `superpowers-code-review` - implementation item formulas: `superpowers-development`, `superpowers-development-item` - vendored skills: `brainstorming`, `writing-plans`, `executing-plans`, `subagent-driven-development`, `requesting-code-review`, `receiving-code-review`, `finishing-a-development-branch`, `test-driven-development`, `verification-before-completion`, and `using-git-worktrees` - provenance: `vendor/superpowers/upstream.toml` records the upstream repository, pinned commit, mit license, and the vendored paths. upstream superpowers includes prompt templates for subagent-driven development. this pack converts that execution handoff into gas city convoy members and drained item formulas: task beads carry task scope, and the item formulas carry the tdd execution procedure. the vendored prompt files are source material only; the workflow must not invoke provider-native subagents, slash commands, task tools, or the upstream plugin runtime. the graph owns all fanout. ## compatibility ledger the pack-local compatibility ledger lives at [`superpowers/requirements.md`](./requirements.md) and records the build-base contract proofs, including the inherited `gc` import, preserved anchor order, mode and drain declarations, the durable tdd step sequence, and the evidence commands that reproduce each claim. 0.1.0 first registry release of the superpowers build factory.", + "search_text": "superpowers gascity-packs maintained gas city obra/superpowers tdd-first skill library as a gas city build factory. https://github.com/gastownhall/gascity-packs/tree/main/superpowers git # superpowers pack this pack runs [superpowers](https://github.com/obra/superpowers), jesse vincent's skill library for disciplined agent development, as a gas city build factory. the superpowers process is: brainstorm a design, get the design and a written spec approved, implement each task with strict test-driven development, then review every task twice (spec compliance first, code quality second) before a final fan-out code review. the pack vendors the upstream skills and maps that process onto gas city's `build-base` workflow contract as the `superpowers-build` formula, so one `gc sling` command runs the whole pipeline. ## when to choose superpowers - you want hard approval gates before any code is written. the requirements stage is two explicit loops — design approval, then written-spec approval — and a failed gate sends the work back through the same loop instead of falling through to implementation. - you want strict tdd per task. every implementation bead runs a durable write-failing-test → verify-it-fails → implement → verify-it-passes sequence as graph steps, not as prose guidance an agent can skip. - you want two-stage review of every task: a spec-compliance pass and a code-quality pass per task, plus a post-implementation code-review and gap-analysis fanout over the whole change. - compared with the siblings: `build-basic` (in the [gascity](../gascity) pack) is the default factory with the fewest gates; [compound-engineering](../compound-engineering) trades approval gates for the widest reviewer-persona fanout; [bmad](../bmad) is document-first (prd/architecture/stories); [gstack](../gstack) adds founder/pm-flavored qa and release-readiness stages. pick superpowers when up-front spec rigor and per-task tdd matter most. ## quick start the goal: from a fresh gas city install to a completed `superpowers-build` run. 1. **install gas city and start a city** (skip what you already have): ```sh brew install gascity gc init ~/my-city cd ~/my-city gc start ``` 2. **add the repository you want agents to work on as a rig:** ```sh git clone https://github.com/you/your-project cd your-project gc rig add . ``` 3. **import the pack.** from the city directory, add `superpowers` at city scope. this writes the import, fetches the latest release, and pins it in `packs.lock` — no clone needed. the pack imports the gas city pack internally as `gc`, so `build-base` and the shared `gc.*` formula surface come along transitively: ```sh gc import add https://github.com/gastownhall/gascity-packs.git//superpowers ``` 4. **import the rig roles** in your city's `city.toml`. each rig that should run work also needs the `gascity/roles` import, which provides the worker agents (`gc.run-operator`, `gc.task-decomposer`, and friends) that formula steps route to; run `gc import install` after editing: ```toml [[rigs]] name = \"your-project\" [rigs.imports.gc] source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\" ``` contributors working on the packs themselves can clone `https://github.com/gastownhall/gascity-packs` and point either `source` at the local path (for example `../gascity-packs/superpowers`) instead. 5. **run your first build.** `superpowers-build` is a targeted formula (`target_required = true`): create a bead describing the goal, then sling the formula against it. `artifact_root` is the only required variable: ```sh gc bd create \"add a --json flag to the export command\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/json-flag/build \\ --var drain_policy=separate ``` 6. **what happens next.** the run walks prepare → brainstorm + spec approval → plan → plan review → decomposition into a task convoy → per-task tdd implementation → code-review/gap-analysis fanout → finalize → publish. stage artifacts (the approved spec, the plan, the decomposition, review reports, and the final report) land under `artifact_root` inside your rig. the publish stage only pushes or opens a pr when you opt in with `push=true` / `open_pr=true`. 7. **watch progress.** list the workflow's beads with `gc bd --rig your-project` and the implementation convoy with `gc convoy --rig your-project`. to inspect the formula surface itself, use `gc formula catalog --json` and `gc formula show superpowers-build --json`. ## stage map `superpowers-build` extends `build-base` and keeps the inherited anchor order `prepare -> requirements -> plan -> plan-review -> decompose -> implement/implement-same-session -> review -> finalize -> publish`. no anchor is renamed, skipped, or reordered, and no top-level stage is added. | `build-base` anchor | `superpowers-build` behavior | | ------------------- | ---------------------------- | | `prepare` | inherited from `build-base`. | | `requirements` | brainstorm + written-spec approval loops (`superpowers-brainstorming` expansion). | | `plan` | plan written with the vendored `writing-plans` skill. | | `plan-review` | plan review loop (`superpowers-plan-review` expansion). | | `decompose` | scope-only task beads + implementation convoy. | | `implement` / `implement-same-session` | tdd development drains (`superpowers-development` / `superpowers-development-item`). | | `review` | code-review + gap-analysis fanout (`superpowers-code-review` expansion). | | `finalize` | vendored `finishing-a-development-branch` skill. | | `publish` | inherited from `build-base`; gated by `push` / `open_pr`. | the native stage formulas extend the matching base methodology contracts: `superpowers-planning` (`planning-base`), `superpowers-decomposition` (`decomposition-base`), `superpowers-implementation` (`implement`), `superpowers-review` (`code-review-base`), and `superpowers-fix-loop` (`fix-loop-base`). `superpowers-build` pins them as its selector defaults (see the customization table) with `implementation_target` defaulting to `superpowers.implementer`. supported modes and drain policies, as declared in `[metadata.gc.methodology]`: - `interaction_modes`: `interactive`, `autonomous`, `headless` (inherited `interaction_mode` var, default `interactive`) - `review_modes`: `report`, `agent`, `interactive` (inherited `review_mode` var, default `agent`) - `implementation_strategy`: `drain` with `allowed_drain_policies` of `separate` (drains `superpowers-development` with exclusive member access) and `same-session` (drains `superpowers-development-item` in one shared single-lane session with `on_item_failure = \"skip_remaining\"`) ### per-task tdd and task review inside both implementation item formulas, the red/green discipline from the vendored `test-driven-development` skill is durable graph structure: each drained task chains `write-failing-test -> verify-test-fails -> implement-change -> verify-test-passes -> task-review -> record-item-result` as formula steps with explicit `needs` edges, so every task's tdd pass is resumable and visible in the graph. superpowers task review is the per-item conversion point for raw `subagent-driven-development`. the `superpowers-task-review` expansion adds spec-compliance and code-quality fanout lanes after each task's tdd pass. those lanes preserve the expected review order without launching provider-native subagents: the spec-compliance lane runs first, the implementation lane applies spec findings, the code-quality lane reviews the corrected task, and the final apply lane owns the `code_review.verdict=done|iterate` loop. ### review/fix loop the review/fix loop is graph structure: the `review` anchor expands `superpowers-code-review`, which fans out sibling `request-code-review` and `gap-analysis-review` lanes, fans in at `process-code-review` (routed to the caller-selected implementation target), and loops through a bounded graph check until the `code_review.verdict=done` approval lands on the workflow root. `superpowers-fix-loop` carries the same review-fix contract for standalone adapter use. ### end-to-end flow ```mermaid flowchart td classdef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827; classdef superpowers fill:#ecfdf5,stroke:#059669,color:#111827; classdef infra fill:#fff7ed,stroke:#d97706,color:#111827; classdef terminal fill:#f9fafb,stroke:#6b7280,color:#111827; start[\"launch superpowers-build\"]:::terminal --> prepare[\"prepare
inherited build-base\"]:::inherited prepare --> reqsetup subgraph requirements[\"requirements override: superpowers brainstorming + written-spec loops\"] direction tb reqsetup[\"setup brainstorming context
superpowers override\"]:::superpowers --> brainstormdesign[\"brainstorm-design
brainstorming skill\"]:::superpowers brainstormdesign --> confirmdesign[\"confirm-design-approval
superpowers approval pass\"]:::superpowers confirmdesign --> designgate{\"design approved?
graph check\"}:::infra designgate -->|no: loop with feedback| brainstormdesign designgate -->|yes| writespec[\"write-requirements-spec
superpowers spec pass\"]:::superpowers writespec --> specreview[\"review-written-spec
spec reviewer prompt\"]:::superpowers specreview --> applyspecfeedback[\"apply-spec-feedback
brainstorming lane\"]:::superpowers applyspecfeedback --> confirmspec[\"confirm-spec-approval
superpowers approval pass\"]:::superpowers confirmspec --> specgate{\"spec approved?
graph check\"}:::infra specgate -->|no: next spec iteration| writespec specgate -->|yes| requirementsdone[\"finalize approved requirements
superpowers override\"]:::superpowers end requirementsdone --> planwrite[\"plan
writing-plans skill override\"]:::superpowers subgraph planreview[\"plan-review override: superpowers plan review loop\"] direction tb planwrite --> planreviewsetup[\"prepare plan review context
superpowers override\"]:::superpowers planreviewsetup --> plandocreview[\"plan-document-review
plan reviewer prompt\"]:::superpowers plandocreview --> applyplanfeedback[\"apply-plan-feedback
writing-plans lane\"]:::superpowers applyplanfeedback --> plangate{\"plan approved?
graph check\"}:::infra plangate -->|no: review revised plan| plandocreview plangate -->|yes| planreviewdone[\"finalize approved plan
superpowers override\"]:::superpowers end planreviewdone --> decompose[\"decompose
superpowers override: scope-only task beads + convoy\"]:::superpowers decompose --> drainchoice{\"drain policy
gas city drain\"}:::infra drainchoice -->|separate| drainseparate[\"separate drain
parallel by convoy deps\"]:::infra drainchoice -->|same-session| drainshared[\"same-session drain
single worker lane\"]:::infra drainseparate --> itemfanout[\"fan out runnable implementation beads\"]:::infra drainshared --> itemserial[\"serialize convoy members\"]:::infra subgraph itemformula[\"implementation item formulas: one formula instance per implementation bead\"] direction tb separateprep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> separateinspect[\"inspect task scope
superpowers implement override\"]:::superpowers sharedinspect[\"inspect shared task scope
superpowers implement-item override\"]:::superpowers separateinspect --> writetest[\"write failing test\"]:::superpowers sharedinspect --> writetest writetest --> verifyred[\"verify test fails\"]:::superpowers verifyred --> code[\"implement minimal change\"]:::superpowers code --> verifygreen[\"verify tests pass\"]:::superpowers verifygreen --> taskreview[\"task-review
spec + quality fanout\"]:::superpowers taskreview --> record[\"record task result\"]:::superpowers record --> closeitem[\"close source anchor
superpowers override\"]:::superpowers end itemfanout --> separateprep itemserial --> sharedinspect closeitem --> drainfanin[\"fan in
drain waits for all item roots\"]:::infra drainfanin --> reviewsetup[\"review
superpowers code-review override\"]:::superpowers subgraph reviewloop[\"post-implementation review loop\"] direction tb reviewsetup --> reviewfanout[\"fan out review lanes\"]:::infra subgraph reviewlanes[\"parallel reviewer lanes: sibling beads with no needs between them\"] direction lr codereview[\"request-code-review lane\"]:::superpowers gapreview[\"gap-analysis-review lane\"]:::superpowers end reviewfanout --> codereview reviewfanout --> gapreview codereview --> reviewfanin[\"fan in
wait for both review artifacts\"]:::infra gapreview --> reviewfanin reviewfanin --> reviewprocess[\"process code-review feedback\"]:::superpowers reviewprocess --> reviewdecision{\"implementation approved?
graph check\"}:::infra reviewdecision -->|no: fix and re-review| reviewfanout reviewdecision -->|yes| reviewdone[\"review approved\"]:::superpowers end reviewdone --> finalize[\"finalize
finishing-a-development-branch override\"]:::superpowers finalize --> publish[\"publish
inherited build-base\"]:::inherited publish --> done[\"workflow complete\"]:::terminal ``` blue nodes are inherited base behavior, green nodes are superpowers-specific overrides, and amber nodes are gas city graph, convoy, or drain infrastructure. the brainstorm and plan phases are explicit approval loops: a failed graph check creates another iteration of the same loop rather than falling through to implementation. the post-implementation review lanes are real fan-out/fan-in graph work — the reviewer beads are siblings with no dependencies between them, and only the feedback-processing bead waits on both review artifacts. in the separate-drain path, independent implementation beads can run in parallel subject to convoy dependencies, each in its own item formula instance with session affinity scoped to that bead. in the same-session path, the drain serializes convoy members through one shared single-lane session. both paths converge before the post-implementation review fan-out. ## customization all variables are passed at launch with `--var name=value`. the first group is inherited from `build-base`; `brainstorming_approval_mode` is superpowers-specific. | variable | default | what it changes | | -------- | ------- | --------------- | | `artifact_root` | required | directory inside the rig where all stage artifacts are written. | | `interaction_mode` | `interactive` | human participation posture for the whole workflow: `interactive`, `autonomous`, or `headless`. | | `brainstorming_approval_mode` | `autonomous` | the stock superpowers \"user approves design?\" gate. `interactive` makes the design and spec approval passes wait on a human gate; `autonomous` drives the same loops without user checkpoints. | | `review_mode` | `agent` | review authority: `report` synthesizes findings without applying fixes; `agent` and `interactive` feed the review-fix lane until the approval check passes. | | `drain_policy` | `separate` | `separate` drains each task in its own worktree and session, in parallel where convoy dependencies allow; `same-session` serializes all tasks through one shared session. | | `implementation_target` | `superpowers.implementer` | role that runs implementation and apply-fix lanes. | | `push` | `false` | set `true` to allow the publish stage to push after all checks pass. | | `open_pr` | `false` | set `true` to allow the publish stage to open a pr after all checks pass. | | `max_iterations` | `10` | bound on implementation/review fix attempts. | | `context_path` | `\"\"` | optional source context bundle path. | | `requirements_path`, `plan_path`, `decomposition_path` | `\"\"` | reuse an existing artifact instead of regenerating it. | | `planning_formula` | `superpowers-planning` | selector: planning methodology formula. | | `decomposition_formula` | `superpowers-decomposition` | selector: decomposition methodology formula. | | `implementation_formula` | `superpowers-implementation` | selector: implementation entry formula. | | `implementation_item_formula` | `superpowers-development-item` | selector: single-item implementation formula for shared drains. | | `code_review_formula` | `superpowers-review` | selector: code-review methodology formula. | | `review_fix_formula` | `superpowers-fix-loop` | selector: review-fix methodology formula. | to change what a stage *says* without changing the graph, shadow its prompt asset. step bodies live at `assets/workflows//.md` and resolve through the normal import/layer search path. for example, to add project-specific planning rules, create `assets/workflows/superpowers-build/plan.md` in your city's asset layer; it replaces this pack's `superpowers/assets/workflows/superpowers-build/plan.md` without touching the formula. for the full contract — including advanced step overrides and the artifact invariants they must preserve — see \"stable workflow override interface\" in the [gascity readme](../gascity/readme.md). ## examples interactive first feature build with human spec approval. `interaction_mode` defaults to `interactive`; setting `brainstorming_approval_mode=interactive` additionally makes the design and written-spec gates wait for your approval before any code is planned or written: ```sh gc bd create \"add rate limiting to the public api\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/rate-limiting/build \\ --var brainstorming_approval_mode=interactive ``` autonomous run that pushes and opens a pr. no human checkpoints: the approval loops, tdd tasks, and review fanout all run unattended, and publish is allowed to push the branch and open a pr once every check passes: ```sh gc bd create \"migrate config loading from json to toml\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/toml-config/build \\ --var interaction_mode=autonomous \\ --var push=true \\ --var open_pr=true ``` same-session drain for a small change. all tasks run serially in one shared worktree and conversation instead of parallel per-task worktrees — useful when the change is small and continuity matters more than parallelism: ```sh gc bd create \"fix off-by-one in pagination cursor\" gc sling gc.run-operator --on superpowers-build \\ --var artifact_root=plans/pagination-fix/build \\ --var drain_policy=same-session ``` ## what's vendored - formula: `superpowers-build` - methodology formulas: `superpowers-planning`, `superpowers-decomposition`, `superpowers-implementation`, `superpowers-review`, and `superpowers-fix-loop` - expansion formulas: `superpowers-brainstorming`, `superpowers-plan-review`, `superpowers-task-review`, and `superpowers-code-review` - implementation item formulas: `superpowers-development`, `superpowers-development-item` - vendored skills: `brainstorming`, `writing-plans`, `executing-plans`, `subagent-driven-development`, `requesting-code-review`, `receiving-code-review`, `finishing-a-development-branch`, `test-driven-development`, `verification-before-completion`, and `using-git-worktrees` - provenance: `vendor/superpowers/upstream.toml` records the upstream repository, pinned commit, mit license, and the vendored paths. upstream superpowers includes prompt templates for subagent-driven development. this pack converts that execution handoff into gas city convoy members and drained item formulas: task beads carry task scope, and the item formulas carry the tdd execution procedure. the vendored prompt files are source material only; the workflow must not invoke provider-native subagents, slash commands, task tools, or the upstream plugin runtime. the graph owns all fanout. ## compatibility ledger the pack-local compatibility ledger lives at [`superpowers/requirements.md`](./requirements.md) and records the build-base contract proofs, including the inherited `gc` import, preserved anchor order, mode and drain declarations, the durable tdd step sequence, and the evidence commands that reproduce each claim. 0.1.0 first registry release of the superpowers build factory. 0.1.1 requirements spec carries requirements.v1 front matter and repairs from validator errors; code review records a validated review report on the workflow root; plan re-validated after plan review; vendors systematic-debugging; picks up newer gascity pack commits (do-work now needs an origin remote).", "readme": { "url": "https://raw.githubusercontent.com/gastownhall/gascity-packs/main/superpowers/README.md", "content": "# Superpowers Pack\n\nThis pack runs [Superpowers](https://github.com/obra/superpowers), Jesse\nVincent's skill library for disciplined agent development, as a Gas City build\nfactory. The Superpowers process is: brainstorm a design, get the design and a\nwritten spec approved, implement each task with strict test-driven development,\nthen review every task twice (spec compliance first, code quality second)\nbefore a final fan-out code review. The pack vendors the upstream skills and\nmaps that process onto Gas City's `build-base` workflow contract as the\n`superpowers-build` formula, so one `gc sling` command runs the whole pipeline.\n\n## When to choose superpowers\n\n- You want hard approval gates before any code is written. The requirements\n stage is two explicit loops — design approval, then written-spec approval —\n and a failed gate sends the work back through the same loop instead of\n falling through to implementation.\n- You want strict TDD per task. Every implementation bead runs a durable\n write-failing-test → verify-it-fails → implement → verify-it-passes\n sequence as graph steps, not as prose guidance an agent can skip.\n- You want two-stage review of every task: a spec-compliance pass and a\n code-quality pass per task, plus a post-implementation code-review and\n gap-analysis fanout over the whole change.\n- Compared with the siblings: `build-basic` (in the [gascity](../gascity)\n pack) is the default factory with the fewest gates;\n [compound-engineering](../compound-engineering) trades approval gates for\n the widest reviewer-persona fanout; [bmad](../bmad) is document-first\n (PRD/architecture/stories); [gstack](../gstack) adds founder/PM-flavored QA\n and release-readiness stages. Pick superpowers when up-front spec rigor and\n per-task TDD matter most.\n\n## Quick start\n\nThe goal: from a fresh Gas City install to a completed `superpowers-build`\nrun.\n\n1. **Install Gas City and start a city** (skip what you already have):\n\n ```sh\n brew install gascity\n gc init ~/my-city\n cd ~/my-city\n gc start\n ```\n\n2. **Add the repository you want agents to work on as a rig:**\n\n ```sh\n git clone https://github.com/you/your-project\n cd your-project\n gc rig add .\n ```\n\n3. **Import the pack.** From the city directory, add `superpowers` at city\n scope. This writes the import, fetches the latest release, and pins it in\n `packs.lock` — no clone needed. The pack imports the Gas City pack\n internally as `gc`, so `build-base` and the shared `gc.*` formula surface\n come along transitively:\n\n ```sh\n gc import add https://github.com/gastownhall/gascity-packs.git//superpowers\n ```\n\n4. **Import the rig roles** in your city's `city.toml`. Each rig that should\n run work also needs the `gascity/roles` import, which provides the worker\n agents (`gc.run-operator`, `gc.task-decomposer`, and friends) that formula\n steps route to; run `gc import install` after editing:\n\n ```toml\n [[rigs]]\n name = \"your-project\"\n\n [rigs.imports.gc]\n source = \"https://github.com/gastownhall/gascity-packs.git//gascity/roles\"\n ```\n\n Contributors working on the packs themselves can clone\n `https://github.com/gastownhall/gascity-packs` and point either `source`\n at the local path (for example `../gascity-packs/superpowers`) instead.\n\n5. **Run your first build.** `superpowers-build` is a targeted formula\n (`target_required = true`): create a bead describing the goal, then sling\n the formula against it. `artifact_root` is the only required variable:\n\n ```sh\n gc bd create \"Add a --json flag to the export command\"\n gc sling gc.run-operator --on superpowers-build \\\n --var artifact_root=plans/json-flag/build \\\n --var drain_policy=separate\n ```\n\n6. **What happens next.** The run walks prepare → brainstorm + spec approval →\n plan → plan review → decomposition into a task convoy → per-task TDD\n implementation → code-review/gap-analysis fanout → finalize → publish.\n Stage artifacts (the approved spec, the plan, the decomposition, review\n reports, and the final report) land under `artifact_root` inside your rig.\n The publish stage only pushes or opens a PR when you opt in with\n `push=true` / `open_pr=true`.\n\n7. **Watch progress.** List the workflow's beads with\n `gc bd --rig your-project` and the implementation convoy with\n `gc convoy --rig your-project`. To inspect the formula surface itself, use\n `gc formula catalog --json` and `gc formula show superpowers-build --json`.\n\n## Stage map\n\n`superpowers-build` extends `build-base` and keeps the inherited anchor order\n`prepare -> requirements -> plan -> plan-review -> decompose ->\nimplement/implement-same-session -> review -> finalize -> publish`. No anchor\nis renamed, skipped, or reordered, and no top-level stage is added.\n\n| `build-base` anchor | `superpowers-build` behavior |\n| ------------------- | ---------------------------- |\n| `prepare` | Inherited from `build-base`. |\n| `requirements` | Brainstorm + written-spec approval loops (`superpowers-brainstorming` expansion). |\n| `plan` | Plan written with the vendored `writing-plans` skill. |\n| `plan-review` | Plan review loop (`superpowers-plan-review` expansion). |\n| `decompose` | Scope-only task beads + implementation convoy. |\n| `implement` / `implement-same-session` | TDD development drains (`superpowers-development` / `superpowers-development-item`). |\n| `review` | Code-review + gap-analysis fanout (`superpowers-code-review` expansion). |\n| `finalize` | Vendored `finishing-a-development-branch` skill. |\n| `publish` | Inherited from `build-base`; gated by `push` / `open_pr`. |\n\nThe native stage formulas extend the matching base methodology contracts:\n`superpowers-planning` (`planning-base`), `superpowers-decomposition`\n(`decomposition-base`), `superpowers-implementation` (`implement`),\n`superpowers-review` (`code-review-base`), and `superpowers-fix-loop`\n(`fix-loop-base`). `superpowers-build` pins them as its selector defaults (see\nthe Customization table) with `implementation_target` defaulting to\n`superpowers.implementer`.\n\nSupported modes and drain policies, as declared in\n`[metadata.gc.methodology]`:\n\n- `interaction_modes`: `interactive`, `autonomous`, `headless` (inherited\n `interaction_mode` var, default `interactive`)\n- `review_modes`: `report`, `agent`, `interactive` (inherited `review_mode`\n var, default `agent`)\n- `implementation_strategy`: `drain` with `allowed_drain_policies` of\n `separate` (drains `superpowers-development` with exclusive member access)\n and `same-session` (drains `superpowers-development-item` in one shared\n single-lane session with `on_item_failure = \"skip_remaining\"`)\n\n### Per-task TDD and task review\n\nInside both implementation item formulas, the red/green discipline from the\nvendored `test-driven-development` skill is durable graph structure: each\ndrained task chains `write-failing-test -> verify-test-fails ->\nimplement-change -> verify-test-passes -> task-review -> record-item-result`\nas formula steps with explicit `needs` edges, so every task's TDD pass is\nresumable and visible in the graph.\n\nSuperpowers task review is the per-item conversion point for raw\n`subagent-driven-development`. The `superpowers-task-review` expansion adds\nspec-compliance and code-quality fanout lanes after each task's TDD pass. Those\nlanes preserve the expected review order without launching provider-native\nsubagents: the spec-compliance lane runs first, the implementation lane applies\nspec findings, the code-quality lane reviews the corrected task, and the final\napply lane owns the `code_review.verdict=done|iterate` loop.\n\n### Review/fix loop\n\nThe review/fix loop is graph structure: the `review` anchor expands\n`superpowers-code-review`, which fans out sibling `request-code-review` and\n`gap-analysis-review` lanes, fans in at `process-code-review` (routed to the\ncaller-selected implementation target), and loops through a bounded graph\ncheck until the `code_review.verdict=done` approval lands on the workflow\nroot. `superpowers-fix-loop` carries the same review-fix contract for\nstandalone adapter use.\n\n### End-to-end flow\n\n```mermaid\nflowchart TD\n classDef inherited fill:#eef2ff,stroke:#4f46e5,color:#111827;\n classDef superpowers fill:#ecfdf5,stroke:#059669,color:#111827;\n classDef infra fill:#fff7ed,stroke:#d97706,color:#111827;\n classDef terminal fill:#f9fafb,stroke:#6b7280,color:#111827;\n\n Start[\"Launch superpowers-build\"]:::terminal --> Prepare[\"prepare
inherited build-base\"]:::inherited\n Prepare --> ReqSetup\n\n subgraph Requirements[\"requirements override: Superpowers brainstorming + written-spec loops\"]\n direction TB\n ReqSetup[\"setup brainstorming context
Superpowers override\"]:::superpowers --> BrainstormDesign[\"brainstorm-design
brainstorming skill\"]:::superpowers\n BrainstormDesign --> ConfirmDesign[\"confirm-design-approval
Superpowers approval pass\"]:::superpowers\n ConfirmDesign --> DesignGate{\"design approved?
graph check\"}:::infra\n DesignGate -->|no: loop with feedback| BrainstormDesign\n DesignGate -->|yes| WriteSpec[\"write-requirements-spec
Superpowers spec pass\"]:::superpowers\n WriteSpec --> SpecReview[\"review-written-spec
spec reviewer prompt\"]:::superpowers\n SpecReview --> ApplySpecFeedback[\"apply-spec-feedback
brainstorming lane\"]:::superpowers\n ApplySpecFeedback --> ConfirmSpec[\"confirm-spec-approval
Superpowers approval pass\"]:::superpowers\n ConfirmSpec --> SpecGate{\"spec approved?
graph check\"}:::infra\n SpecGate -->|no: next spec iteration| WriteSpec\n SpecGate -->|yes| RequirementsDone[\"finalize approved requirements
Superpowers override\"]:::superpowers\n end\n\n RequirementsDone --> PlanWrite[\"plan
writing-plans skill override\"]:::superpowers\n\n subgraph PlanReview[\"plan-review override: Superpowers plan review loop\"]\n direction TB\n PlanWrite --> PlanReviewSetup[\"prepare plan review context
Superpowers override\"]:::superpowers\n PlanReviewSetup --> PlanDocReview[\"plan-document-review
plan reviewer prompt\"]:::superpowers\n PlanDocReview --> ApplyPlanFeedback[\"apply-plan-feedback
writing-plans lane\"]:::superpowers\n ApplyPlanFeedback --> PlanGate{\"plan approved?
graph check\"}:::infra\n PlanGate -->|no: review revised plan| PlanDocReview\n PlanGate -->|yes| PlanReviewDone[\"finalize approved plan
Superpowers override\"]:::superpowers\n end\n\n PlanReviewDone --> Decompose[\"decompose
Superpowers override: scope-only task beads + convoy\"]:::superpowers\n Decompose --> DrainChoice{\"drain policy
Gas City drain\"}:::infra\n DrainChoice -->|separate| DrainSeparate[\"separate drain
parallel by convoy deps\"]:::infra\n DrainChoice -->|same-session| DrainShared[\"same-session drain
single worker lane\"]:::infra\n\n DrainSeparate --> ItemFanout[\"fan out runnable implementation beads\"]:::infra\n DrainShared --> ItemSerial[\"serialize convoy members\"]:::infra\n\n subgraph ItemFormula[\"implementation item formulas: one formula instance per implementation bead\"]\n direction TB\n SeparatePrep[\"prepare-worktree
inherited do-work
separate drain only\"]:::inherited --> SeparateInspect[\"inspect task scope
Superpowers implement override\"]:::superpowers\n SharedInspect[\"inspect shared task scope
Superpowers implement-item override\"]:::superpowers\n SeparateInspect --> WriteTest[\"write failing test\"]:::superpowers\n SharedInspect --> WriteTest\n WriteTest --> VerifyRed[\"verify test fails\"]:::superpowers\n VerifyRed --> Code[\"implement minimal change\"]:::superpowers\n Code --> VerifyGreen[\"verify tests pass\"]:::superpowers\n VerifyGreen --> TaskReview[\"task-review
spec + quality fanout\"]:::superpowers\n TaskReview --> Record[\"record task result\"]:::superpowers\n Record --> CloseItem[\"close source anchor
Superpowers override\"]:::superpowers\n end\n\n ItemFanout --> SeparatePrep\n ItemSerial --> SharedInspect\n CloseItem --> DrainFanIn[\"fan in
drain waits for all item roots\"]:::infra\n\n DrainFanIn --> ReviewSetup[\"review
Superpowers code-review override\"]:::superpowers\n\n subgraph ReviewLoop[\"post-implementation review loop\"]\n direction TB\n ReviewSetup --> ReviewFanout[\"fan out review lanes\"]:::infra\n\n subgraph ReviewLanes[\"parallel reviewer lanes: sibling beads with no needs between them\"]\n direction LR\n CodeReview[\"request-code-review lane\"]:::superpowers\n GapReview[\"gap-analysis-review lane\"]:::superpowers\n end\n\n ReviewFanout --> CodeReview\n ReviewFanout --> GapReview\n CodeReview --> ReviewFanIn[\"fan in
wait for both review artifacts\"]:::infra\n GapReview --> ReviewFanIn\n ReviewFanIn --> ReviewProcess[\"process code-review feedback\"]:::superpowers\n ReviewProcess --> ReviewDecision{\"implementation approved?
graph check\"}:::infra\n ReviewDecision -->|no: fix and re-review| ReviewFanout\n ReviewDecision -->|yes| ReviewDone[\"review approved\"]:::superpowers\n end\n\n ReviewDone --> Finalize[\"finalize
finishing-a-development-branch override\"]:::superpowers\n Finalize --> Publish[\"publish
inherited build-base\"]:::inherited\n Publish --> Done[\"workflow complete\"]:::terminal\n```\n\nBlue nodes are inherited base behavior, green nodes are Superpowers-specific\noverrides, and amber nodes are Gas City graph, convoy, or drain infrastructure.\nThe brainstorm and plan phases are explicit approval loops: a failed graph\ncheck creates another iteration of the same loop rather than falling through\nto implementation. The post-implementation review lanes are real\nfan-out/fan-in graph work — the reviewer beads are siblings with no\ndependencies between them, and only the feedback-processing bead waits on both\nreview artifacts.\n\nIn the separate-drain path, independent implementation beads can run in\nparallel subject to convoy dependencies, each in its own item formula instance\nwith session affinity scoped to that bead. In the same-session path, the drain\nserializes convoy members through one shared single-lane session. Both paths\nconverge before the post-implementation review fan-out.\n\n## Customization\n\nAll variables are passed at launch with `--var name=value`. The first group is\ninherited from `build-base`; `brainstorming_approval_mode` is\nSuperpowers-specific.\n\n| Variable | Default | What it changes |\n| -------- | ------- | --------------- |\n| `artifact_root` | required | Directory inside the rig where all stage artifacts are written. |\n| `interaction_mode` | `interactive` | Human participation posture for the whole workflow: `interactive`, `autonomous`, or `headless`. |\n| `brainstorming_approval_mode` | `autonomous` | The stock Superpowers \"User approves design?\" gate. `interactive` makes the design and spec approval passes wait on a human gate; `autonomous` drives the same loops without user checkpoints. |\n| `review_mode` | `agent` | Review authority: `report` synthesizes findings without applying fixes; `agent` and `interactive` feed the review-fix lane until the approval check passes. |\n| `drain_policy` | `separate` | `separate` drains each task in its own worktree and session, in parallel where convoy dependencies allow; `same-session` serializes all tasks through one shared session. |\n| `implementation_target` | `superpowers.implementer` | Role that runs implementation and apply-fix lanes. |\n| `push` | `false` | Set `true` to allow the publish stage to push after all checks pass. |\n| `open_pr` | `false` | Set `true` to allow the publish stage to open a PR after all checks pass. |\n| `max_iterations` | `10` | Bound on implementation/review fix attempts. |\n| `context_path` | `\"\"` | Optional source context bundle path. |\n| `requirements_path`, `plan_path`, `decomposition_path` | `\"\"` | Reuse an existing artifact instead of regenerating it. |\n| `planning_formula` | `superpowers-planning` | Selector: planning methodology formula. |\n| `decomposition_formula` | `superpowers-decomposition` | Selector: decomposition methodology formula. |\n| `implementation_formula` | `superpowers-implementation` | Selector: implementation entry formula. |\n| `implementation_item_formula` | `superpowers-development-item` | Selector: single-item implementation formula for shared drains. |\n| `code_review_formula` | `superpowers-review` | Selector: code-review methodology formula. |\n| `review_fix_formula` | `superpowers-fix-loop` | Selector: review-fix methodology formula. |\n\nTo change what a stage *says* without changing the graph, shadow its prompt\nasset. Step bodies live at `assets/workflows//.md` and\nresolve through the normal import/layer search path. For example, to add\nproject-specific planning rules, create\n`assets/workflows/superpowers-build/plan.md` in your city's asset layer; it\nreplaces this pack's `superpowers/assets/workflows/superpowers-build/plan.md`\nwithout touching the formula. For the full contract — including advanced step\noverrides and the artifact invariants they must preserve — see \"Stable\nWorkflow Override Interface\" in the [gascity README](../gascity/README.md).\n\n## Examples\n\nInteractive first feature build with human spec approval. `interaction_mode`\ndefaults to `interactive`; setting `brainstorming_approval_mode=interactive`\nadditionally makes the design and written-spec gates wait for your approval\nbefore any code is planned or written:\n\n```sh\ngc bd create \"Add rate limiting to the public API\"\ngc sling gc.run-operator --on superpowers-build \\\n --var artifact_root=plans/rate-limiting/build \\\n --var brainstorming_approval_mode=interactive\n```\n\nAutonomous run that pushes and opens a PR. No human checkpoints: the approval\nloops, TDD tasks, and review fanout all run unattended, and publish is allowed\nto push the branch and open a PR once every check passes:\n\n```sh\ngc bd create \"Migrate config loading from JSON to TOML\"\ngc sling gc.run-operator --on superpowers-build \\\n --var artifact_root=plans/toml-config/build \\\n --var interaction_mode=autonomous \\\n --var push=true \\\n --var open_pr=true\n```\n\nSame-session drain for a small change. All tasks run serially in one shared\nworktree and conversation instead of parallel per-task worktrees — useful when\nthe change is small and continuity matters more than parallelism:\n\n```sh\ngc bd create \"Fix off-by-one in pagination cursor\"\ngc sling gc.run-operator --on superpowers-build \\\n --var artifact_root=plans/pagination-fix/build \\\n --var drain_policy=same-session\n```\n\n## What's vendored\n\n- Formula: `superpowers-build`\n- Methodology formulas: `superpowers-planning`, `superpowers-decomposition`,\n `superpowers-implementation`, `superpowers-review`, and\n `superpowers-fix-loop`\n- Expansion formulas: `superpowers-brainstorming`, `superpowers-plan-review`,\n `superpowers-task-review`, and `superpowers-code-review`\n- Implementation item formulas: `superpowers-development`,\n `superpowers-development-item`\n- Vendored skills: `brainstorming`, `writing-plans`, `executing-plans`,\n `subagent-driven-development`, `requesting-code-review`,\n `receiving-code-review`, `finishing-a-development-branch`,\n `test-driven-development`, `verification-before-completion`, and\n `using-git-worktrees`\n- Provenance: `vendor/superpowers/upstream.toml` records the upstream\n repository, pinned commit, MIT license, and the vendored paths.\n\nUpstream Superpowers includes prompt templates for subagent-driven\ndevelopment. This pack converts that execution handoff into Gas City convoy\nmembers and drained item formulas: task beads carry task scope, and the item\nformulas carry the TDD execution procedure. The vendored prompt files are\nsource material only; the workflow must not invoke provider-native subagents,\nslash commands, task tools, or the upstream plugin runtime. The graph owns all\nfanout.\n\n## Compatibility ledger\n\nThe pack-local compatibility ledger lives at\n[`superpowers/REQUIREMENTS.md`](./REQUIREMENTS.md) and records the build-base\ncontract proofs, including the inherited `gc` import, preserved anchor order,\nmode and drain declarations, the durable TDD step sequence, and the evidence\ncommands that reproduce each claim." }, "og_image": "/og/superpowers.svg", - "latest": "0.1.0", + "latest": "0.1.1", "releases": [ { "version": "0.1.0", @@ -582,6 +622,14 @@ "hash": "sha256:ba0428bf51627bf465a693f2426cf6be0cef70ae81b043fce3ce6761a4772290", "description": "First registry release of the superpowers build factory.", "withdrawn": false + }, + { + "version": "0.1.1", + "ref": "main", + "commit": "390cac1a8a0478b6dfde603dae00a43ed9748d13", + "hash": "sha256:4358803ef87a4209a90f0c000fdcc58d7f92d04a9db471474d0b0ce2f0054223", + "description": "Requirements spec carries requirements.v1 front matter and repairs from validator errors; code review records a validated review report on the workflow root; plan re-validated after plan review; vendors systematic-debugging; picks up newer gascity pack commits (do-work now needs an origin remote).", + "withdrawn": false } ] } diff --git a/public/og/bmad.svg b/public/og/bmad.svg index c7b3537..2ac5471 100644 --- a/public/og/bmad.svg +++ b/public/og/bmad.svg @@ -29,10 +29,10 @@ latest - v0.1.0 + v0.1.1 releases - 1 + 2 state active diff --git a/public/og/compound-engineering.svg b/public/og/compound-engineering.svg index 48d0981..4d02cb5 100644 --- a/public/og/compound-engineering.svg +++ b/public/og/compound-engineering.svg @@ -29,10 +29,10 @@ latest - v0.1.0 + v0.1.1 releases - 1 + 2 state active diff --git a/public/og/contributing.svg b/public/og/contributing-to-gascity.svg similarity index 92% rename from public/og/contributing.svg rename to public/og/contributing-to-gascity.svg index a6a9543..47d40ab 100644 --- a/public/og/contributing.svg +++ b/public/og/contributing-to-gascity.svg @@ -1,4 +1,4 @@ - + @@ -24,15 +24,15 @@ gascity-packs / git - CONTRIBUTING + CONTRIBUTING-TO-GASCITY The external-contributor lifecycle for gastownhall/gascity — write agood issue, find priority work, plan a PR, map its blast radius, andself-review against the codebase audit. Each step produces an artifact... latest - v0.2.1 + v0.5.0 releases - 3 + 1 state active diff --git a/public/og/gstack.svg b/public/og/gstack.svg index 3f4bb39..fa1bcda 100644 --- a/public/og/gstack.svg +++ b/public/og/gstack.svg @@ -29,10 +29,10 @@ latest - v0.1.0 + v0.1.1 releases - 1 + 2 state active diff --git a/public/og/registry.svg b/public/og/registry.svg index 0461556..f5985b6 100644 --- a/public/og/registry.svg +++ b/public/og/registry.svg @@ -32,7 +32,7 @@ 16 releases - 33 + 39 sources 1 diff --git a/public/og/slack-channel.svg b/public/og/slack-channel.svg index d2ae46f..53ae5c0 100644 --- a/public/og/slack-channel.svg +++ b/public/og/slack-channel.svg @@ -29,10 +29,10 @@ latest - v0.1.1 + v0.1.2 releases - 2 + 3 state active diff --git a/public/og/slack-full.svg b/public/og/slack-full.svg index 1179afd..1766548 100644 --- a/public/og/slack-full.svg +++ b/public/og/slack-full.svg @@ -29,10 +29,10 @@ latest - v0.0.3 + v0.0.5 releases - 2 + 4 state active diff --git a/public/og/slack-mini.svg b/public/og/slack-mini.svg index 79ca582..53e3690 100644 --- a/public/og/slack-mini.svg +++ b/public/og/slack-mini.svg @@ -29,10 +29,10 @@ latest - v0.1.0 + v0.1.1 releases - 1 + 2 state active diff --git a/public/og/superpowers.svg b/public/og/superpowers.svg index 46ad272..e2a3554 100644 --- a/public/og/superpowers.svg +++ b/public/og/superpowers.svg @@ -29,10 +29,10 @@ latest - v0.1.0 + v0.1.1 releases - 1 + 2 state active diff --git a/public/registry.toml b/public/registry.toml index 825fafd..9f3d28e 100644 --- a/public/registry.toml +++ b/public/registry.toml @@ -18,6 +18,13 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci hash = "sha256:cb81d9e35fe4ca2263922e901eac34aec6183d92832aa0695e43ff038462087d" description = "First registry release of the BMAD build factory." + [[pack.release]] + version = "0.1.1" + ref = "main" + commit = "390cac1a8a0478b6dfde603dae00a43ed9748d13" + hash = "sha256:f59ebe28181a208086a7018d1c6df4396894c22be9bd272e025312feb7879a0a" + description = "Implementer commits in the source-anchor worktree (only real changes); code review records a validated review report on the workflow root; apply-findings keeps artifacts schema-valid; picks up newer gascity pack commits (do-work now needs an origin remote)." + [[pack]] pack_key = "gascity-packs--cass" registry = "gascity-packs" @@ -52,36 +59,29 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci hash = "sha256:7981abfa28021a99accacc097d32a04e2d28c344f09ebf17e3f0ca51768140af" description = "First registry release of the Compound Engineering build factory." + [[pack.release]] + version = "0.1.1" + ref = "main" + commit = "390cac1a8a0478b6dfde603dae00a43ed9748d13" + hash = "sha256:12bf7fbd2ab86d0fc2a866ac7562bc957908eae408c779e539af3c3d14ede4b9" + description = "Implementer commits in the source-anchor worktree; code review and compound resolution record validated report paths on the workflow root; apply-findings keeps artifacts schema-valid and the plan is re-validated after plan review; picks up newer gascity pack commits (do-work now needs an origin remote)." + [[pack]] - pack_key = "gascity-packs--contributing" + pack_key = "gascity-packs--contributing-to-gascity" registry = "gascity-packs" - name = "contributing" + name = "contributing-to-gascity" tier = "maintained" publisher = "Gas City" description = "The external-contributor lifecycle for gastownhall/gascity — write a good issue, find priority work, plan a PR, map its blast radius, and self-review against the codebase audit. Each step produces an artifact you act on; the pack stops before pushing — opening the PR is your call. Self-contained: gas-city's actual standards (the B-rule adoption-review audit, blast-radius dimensions, test tiers) are baked into the skills; no imports." - source = "https://github.com/gastownhall/gascity-packs/tree/main/contributing" + source = "https://github.com/gastownhall/gascity-packs/tree/main/contributing-to-gascity" source_kind = "git" [[pack.release]] - version = "0.1.0" + version = "0.5.0" ref = "main" - commit = "16f8297d4e62e143456bfbe4a00e0980a7d9400e" - hash = "sha256:8ee58fba7a87a2390fe707ccb593eec3f25b303782696d836d800e68992beeef" - description = "Initial contributing pack release." - - [[pack.release]] - version = "0.2.0" - ref = "main" - commit = "7dd57dd9be7d286f19f74c27a00a4359392734a4" - hash = "sha256:3ba4ad08c81ab8d2cb9bd591a0f546435e14a9132b1d4d4745bde03d0feb94ad" - description = "Self-contained gas-city contributor lifecycle: drops the pr-pipeline import, bakes in the B-rule adoption-review audit, blast-radius dimensions, and test-tier strategy (7 skills, zero imports)." - - [[pack.release]] - version = "0.2.1" - ref = "main" - commit = "5b7c5c1320ee6537f5b9c661d2e52712140320df" - hash = "sha256:3757a3dee523efb29e7cf1f9927c70c9bf491eba6d8416e147430aa6130702cf" - description = "YAML-safe skill front matter (0.2.0 had an unquoted colon that broke parsing) plus a durability pass — removes time-pinned references (PR-number citations, dated product names, build-timeout numbers, drift-prone counts) so the skills resist staleness. No behavior change." + commit = "510a4e22a34d325063fe24f77038c004221b7e7e" + hash = "sha256:e4d85af1e0e202e30785ac33ac12c83f26dba1cc61de22573d3ac5f8ea9e80de" + description = "First release under the new name; the pack was renamed from contributing. Carries everything since contributing 0.2.1: the mol-contributing-* formula set, renamed lifecycle skills with an orchestrated umbrella, default-branch resolution from the local ref, and bead commands routed through gc. Imports must switch from //contributing to //contributing-to-gascity." [[pack]] pack_key = "gascity-packs--discord" @@ -259,6 +259,13 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci hash = "sha256:4f180f1327933239a29918e661c0cd3a54ff3de84ab981324665e776de2eff8f" description = "First registry release of the gstack build factory." + [[pack.release]] + version = "0.1.1" + ref = "main" + commit = "390cac1a8a0478b6dfde603dae00a43ed9748d13" + hash = "sha256:ed74c2b288875a5f563d2b0d2a5afffab048a4136b237fb94d6e7baf60031cba" + description = "Implementer commits in the source-anchor worktree; code review records a validated review report on the workflow root; apply-findings keeps artifacts schema-valid and the plan is re-validated after plan review; picks up newer gascity pack commits (do-work now needs an origin remote)." + [[pack]] pack_key = "gascity-packs--oversight-rig" registry = "gascity-packs" @@ -334,6 +341,13 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci hash = "sha256:e8da9a9dab6e6d3ce8a3cded94104a1c64d88f39130e63b09653ce3e7457dd36" description = "Ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark schema test executable to align release hash with materialized modes." + [[pack.release]] + version = "0.1.2" + ref = "main" + commit = "ea4066504ef753c0b4e8bcc73aea2d447c964969" + hash = "sha256:a96786bd538a592dc7eb259adefc2f5d731aafc2724582133bab777a89c00e5b" + description = "Socket Mode transport, adapter registration retry during city startup, a checked-in run.sh that rebuilds a missing adapter binary, and install docs for the git import." + [[pack]] pack_key = "gascity-packs--slack-full" registry = "gascity-packs" @@ -358,6 +372,20 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci hash = "sha256:2d2255e68175b7d464845100654839a0956e8cd6fefa259e85cd03915213186b" description = "Ship idempotent reply-current (stops delivered-but-timed-out double-post) and mark pack scripts/tests executable to align release hash with materialized modes." + [[pack.release]] + version = "0.0.4" + ref = "main" + commit = "ea4066504ef753c0b4e8bcc73aea2d447c964969" + hash = "sha256:00d3af738603107cc507136d9ed3cb3f3e88f3f4834e88a98cd5b0564872cbb1" + description = "Rolls up main since 0.0.3: company rooms, per-agent and group DMs, delivery that wakes sleeping sessions, a run.sh entrypoint that rebuilds a missing adapter, thread-root context on every threaded reply, app manifests that Slack accepts on import, and gc slack status reporting unreadable sections instead of empty ones." + + [[pack.release]] + version = "0.0.5" + ref = "main" + commit = "8e95b894d2b24c1aa9b59688e587e5914a169e47" + hash = "sha256:9aabd24333cd1790b3b60ef50349e2a874c07ba967433fa21caf9034fa51ea4d" + description = "The adapter start script builds in private per-user directories when HOME is unset, instead of shared fixed paths under TMPDIR that another local user could create first." + [[pack]] pack_key = "gascity-packs--slack-mini" registry = "gascity-packs" @@ -375,6 +403,13 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci hash = "sha256:fff2523a9ab32e0812815c8d2addb08e6349d3885052d095bd76600eec87921a" description = "Initial Slack mini pack release with canonical content hash." + [[pack.release]] + version = "0.1.1" + ref = "main" + commit = "ea4066504ef753c0b4e8bcc73aea2d447c964969" + hash = "sha256:59996bf0c9dcdedf72a3f058cb13cfb421042fc4099d7beb5d03900523800324" + description = "Socket Mode transport for ingress-free installs, a checked-in run.sh that rebuilds a missing adapter binary, and install docs for the git import." + [[pack]] pack_key = "gascity-packs--superpowers" registry = "gascity-packs" @@ -391,3 +426,10 @@ featured_pack_keys = ["gascity-packs--gascity", "gascity-packs--gastown", "gasci commit = "636fdfaaf54195b293e8e56d5e9c30e4b8d5a9ef" hash = "sha256:ba0428bf51627bf465a693f2426cf6be0cef70ae81b043fce3ce6761a4772290" description = "First registry release of the superpowers build factory." + + [[pack.release]] + version = "0.1.1" + ref = "main" + commit = "390cac1a8a0478b6dfde603dae00a43ed9748d13" + hash = "sha256:4358803ef87a4209a90f0c000fdcc58d7f92d04a9db471474d0b0ce2f0054223" + description = "Requirements spec carries requirements.v1 front matter and repairs from validator errors; code review records a validated review report on the workflow root; plan re-validated after plan review; vendors systematic-debugging; picks up newer gascity pack commits (do-work now needs an origin remote)." diff --git a/sources.toml b/sources.toml index 7ec6c52..f9e042a 100644 --- a/sources.toml +++ b/sources.toml @@ -19,7 +19,7 @@ expected_packs = [ "bmad", "cass", "compound-engineering", - "contributing", + "contributing-to-gascity", "discord", "gascity", "gastown", From 8dfcac0a3ff3e6b0a010f95b3efe233b4829000e Mon Sep 17 00:00:00 2001 From: Julian Knutsen Date: Fri, 23 Oct 2026 23:28:21 +0000 Subject: [PATCH 3/3] ci: pin overrides to the ranges their consumers declare - brace-expansion: override >=5.0.11 -> ^2.1.7. Its only consumer (minimatch 5.x via @redocly/openapi-core) declares ^2.0.1, and 5.x breaks it ("expand is not a function"). 2.1.7 is the patched 2.x release for GHSA-qhr7-859c-m2p7 (2.1.6), GHSA-6j4f-fj2g-mc7p (2.1.5) and GHSA-q2hr-2g5m-vwhr (2.1.7). - postcss: override floor -> ^8.5.30, which vite 8.3.x declares. - vite: ^8.1.6 -> ^8.3.3, so the range excludes the vulnerable 8.2.0-8.2.3 and 8.3.0-8.3.2. bun audit is clean. Co-Authored-By: Claude Opus 5.5 (1M context) --- bun.lock | 12 ++++++------ package.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bun.lock b/bun.lock index 4cf6d93..8654bd8 100644 --- a/bun.lock +++ b/bun.lock @@ -28,15 +28,15 @@ "@vitejs/plugin-react": "^6.0.2", "openapi-typescript": "^7.13.0", "typescript": "~6.0.3", - "vite": "^8.1.6", + "vite": "^8.3.3", }, }, }, "overrides": { - "brace-expansion": ">=5.0.11", + "brace-expansion": "^2.1.7", "js-yaml": "^4.3.2", "nanoid": "^3.3.18", - "postcss": ">=8.5.18", + "postcss": "^8.5.30", "source-map-js": "^1.2.2", }, "packages": { @@ -142,11 +142,11 @@ "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="], - "brace-expansion": ["brace-expansion@5.0.12", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ=="], + "brace-expansion": ["brace-expansion@2.1.7", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g=="], "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], @@ -362,7 +362,7 @@ "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], - "postcss": ["postcss@8.5.23", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg=="], + "postcss": ["postcss@8.5.31", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg=="], "postgres": ["postgres@3.4.9", "", {}, "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw=="], diff --git a/package.json b/package.json index b09e039..5b57946 100644 --- a/package.json +++ b/package.json @@ -46,13 +46,13 @@ "@vitejs/plugin-react": "^6.0.2", "openapi-typescript": "^7.13.0", "typescript": "~6.0.3", - "vite": "^8.1.6" + "vite": "^8.3.3" }, "overrides": { - "brace-expansion": ">=5.0.11", + "brace-expansion": "^2.1.7", "js-yaml": "^4.3.2", "nanoid": "^3.3.18", - "postcss": ">=8.5.18", + "postcss": "^8.5.30", "source-map-js": "^1.2.2" } }