name: normalize-bun-monorepo graph: ci: done: role: solve-issue-workflow prompt: CI configured. Register solve-issue workflow for repo at {{{repoPath}}}. failed: role: solve-issue-workflow prompt: CI setup failed ({{{reason}}}), but continue. Register solve-issue workflow for repo at {{{repoPath}}}. biome: done: role: package-metadata prompt: Biome configured. Standardize package metadata for repo at {{{repoPath}}}. failed: role: package-metadata prompt: Biome setup failed ({{{reason}}}), but continue. Standardize package metadata for repo at {{{repoPath}}}. $START: _: role: workspace prompt: Set up bun workspace structure for repo at {{{repoPath}}}. release: done: role: testing prompt: Release pipeline configured. Set up vitest for repo at {{{repoPath}}}. failed: role: testing prompt: Release pipeline failed ({{{reason}}}), but continue. Set up vitest for repo at {{{repoPath}}}. testing: done: role: ci prompt: Testing configured. Set up Gitea CI for repo at {{{repoPath}}}. failed: role: ci prompt: Testing setup failed ({{{reason}}}), but continue. Set up Gitea CI for repo at {{{repoPath}}}. committer: failed: role: $END prompt: "Commit failed: {{{reason}}}." committed: role: $END prompt: "Normalization committed: {{{commitHash}}}." no_changes: role: $END prompt: Repo already normalized, no changes needed. workspace: done: role: typescript prompt: Workspace ready. Configure TypeScript for repo at {{{repoPath}}}. failed: role: typescript prompt: Workspace setup failed ({{{reason}}}), but continue. Configure TypeScript for repo at {{{repoPath}}}. guardrails: done: role: committer prompt: All normalization complete. Commit changes in repo at {{{repoPath}}}. failed: role: committer prompt: Guardrails failed ({{{reason}}}), but commit whatever was done in repo at {{{repoPath}}}. typescript: done: role: biome prompt: TypeScript configured. Set up Biome for repo at {{{repoPath}}}. failed: role: biome prompt: TypeScript setup failed ({{{reason}}}), but continue. Set up Biome for repo at {{{repoPath}}}. package-metadata: done: role: release prompt: Package metadata standardized. Configure release pipeline for repo at {{{repoPath}}}. failed: role: release prompt: Package metadata failed ({{{reason}}}), but continue. Configure release pipeline for repo at {{{repoPath}}}. solve-issue-workflow: done: role: guardrails prompt: Solve-issue workflow registered. Install guardrails for repo at {{{repoPath}}}. failed: role: guardrails prompt: Solve-issue workflow failed ({{{reason}}}), but continue. Install guardrails for repo at {{{repoPath}}}. roles: ci: goal: You configure Gitea Actions CI for build, lint, and test on push/PR. output: Describe the CI pipeline configured. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. IMPORTANT: If `.gitea/workflows/ci.yml` already exists, review it for completeness but don't overwrite unless it's missing key steps. If `.github/workflows/` exists (GitHub Actions), keep it — add `.gitea/workflows/` alongside it. Create `.gitea/workflows/ci.yml` (if not present): ```yaml name: CI on: push: branches: ['*'] pull_request: branches: [main] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install - name: Build run: bun run build - name: Lint run: bun run check - name: Test run: bun run test:ci ``` ## Verification ```bash # 1. CI file exists test -f .gitea/workflows/ci.yml # 2. YAML is valid node -e " const fs = require('fs'); const content = fs.readFileSync('.gitea/workflows/ci.yml', 'utf8'); if (!content.includes('bun')) { console.error('Missing bun setup'); process.exit(1); } if (!content.includes('test')) { console.error('Missing test step'); process.exit(1); } console.log('CI YAML looks valid'); " # 3. Required root scripts exist for CI to work node -e " const pkg = require('./package.json'); const required = ['build', 'check', 'test:ci']; const missing = required.filter(s => !pkg.scripts?.[s]); if (missing.length) { console.error('Missing scripts for CI:', missing.join(', ')); process.exit(1); } console.log('All CI-required scripts present'); " ``` Post-condition: CI file exists, YAML references bun and test, all required scripts exist in package.json. description: Set up Gitea CI workflow frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - ci-config biome: goal: You configure Biome for consistent code quality across the monorepo. output: List what was configured and any remaining lint issues. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. IMPORTANT: Be idempotent — if biome.json already exists, merge missing settings rather than overwriting. Check and fix: 1. Install biome: add `@biomejs/biome` to root devDependencies (skip if already present) 2. Root `biome.json` must exist with at minimum: - `files.includes`: `["**", "!**/dist", "!**/node_modules"]` - `formatter`: indentStyle space, indentWidth 2, lineWidth 100 - `javascript.formatter`: quoteStyle double, semicolons always - `linter.rules.nursery.noConsole: "error"` (production code) - Override for test files (`**/__tests__/**`): `noConsole: "off"`, `noExplicitAny: "off"` - `assist.actions.source.organizeImports: "on"` If biome.json already exists, only add missing fields — preserve existing customizations. 3. Root scripts must include: `"check"` (should include `biome check .`), `"format": "biome format --write ."` 4. Run `bunx biome check .` — fix auto-fixable issues with `bunx biome check . --fix` 5. Remaining unfixable issues: list them but don't block ## Verification ```bash # 1. biome.json exists test -f biome.json # 2. biome is installed bunx biome --version # 3. check runs (exit 0 or list remaining issues) bunx biome check . 2>&1 || true ``` Post-condition: `biome.json` exists, `bunx biome check .` runs (may have warnings but no infrastructure errors). description: Configure Biome linter and formatter frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - linter-config release: goal: "You set up the complete release pipeline: changesets for version management, publish script for npm release." output: Describe what was configured. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. IMPORTANT: Be idempotent — skip steps that are already done. ## Part 1: Changesets 1. Install: add `@changesets/cli` to root devDependencies (skip if present), run `bun install` 2. If `.changeset/config.json` does not exist, run `bunx changeset init` 3. Verify `.changeset/config.json` has: - `"access": "public"` (for @scoped packages) - `"baseBranch": "main"` 4. Add `.changeset/README.md` if missing ## Part 2: Publish Script 5. Create `scripts/publish-all.mjs` (skip if already exists and looks correct): - Scan `packages/` for non-private packages (skip `"private": true`) - Determine publish order by resolving workspace dependency graph (dependees before dependents) - For each package in order: a. Replace `"workspace:^"` deps with actual versions from workspace b. Run `npm publish --access public --ignore-scripts` (MUST use --ignore-scripts to bypass prepublishOnly guardrail) c. Restore original package.json - Support flags: `--dry-run`, `--tag ` - Handle errors: if one package fails, restore and continue CRITICAL: The publish command MUST include `--ignore-scripts` because the guardrails role adds a `prepublishOnly` script that blocks direct publishing. ## Part 3: Root Scripts 6. Root scripts must include: - `"changeset": "bunx changeset"` - `"version": "bunx changeset version"` - `"release": "bun run build && bun run test && node scripts/publish-all.mjs"` ## Verification ```bash # 1. changeset config exists and is valid test -f .changeset/config.json node -e "const c = require('./.changeset/config.json'); console.log('access:', c.access, 'baseBranch:', c.baseBranch)" # 2. changeset status works bunx changeset status 2>&1 || true # 3. publish script exists and uses --ignore-scripts test -f scripts/publish-all.mjs grep -q 'ignore-scripts' scripts/publish-all.mjs # 4. dry run works node scripts/publish-all.mjs --dry-run ``` Post-condition: All verification commands pass. description: Configure changesets and publish pipeline frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - changeset-config - release-config testing: goal: You set up vitest test infrastructure across the monorepo. output: List what was configured per package. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. IMPORTANT: Be idempotent — do NOT overwrite existing vitest.config.ts or test files. Check and fix: 1. Add `vitest` to root devDependencies (skip if present), run `bun install` 2. For each package under `packages/`: - If `vitest.config.ts` does NOT already exist, create it: ```ts import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["src/__tests__/**/*.test.ts"], }, }); ``` - If package.json has no `"test"` script, add: `"test": "vitest run --passWithNoTests"`, `"test:ci": "vitest run --passWithNoTests"` - Create `src/__tests__/` directory if it doesn't exist 3. Root package.json scripts must include: - `"test": "bun run --filter './packages/*' test"` - `"test:ci": "bun run --filter './packages/*' test:ci"` ## Verification ```bash # 1. vitest is installed bunx vitest --version # 2. each package has vitest config for d in packages/*/; do if [ -f "$d/vitest.config.ts" ]; then echo "$d: ✅"; else echo "$d: ❌ missing vitest.config.ts"; fi done # 3. root test script works bun run test 2>&1 || true ``` Post-condition: `bun run test` runs without infrastructure errors (no tests is OK, test failures are OK). description: Configure vitest for all packages frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - test-config committer: goal: You commit all the changes made by previous roles in a single clean commit. output: List files changed and commit hash. Set $status to committed or no_changes. procedure: | cd into the repo path from your task prompt. 1. Review all changes: `git diff --stat` and `git status` 2. Verify key files exist from previous roles (spot-check): ```bash echo "=== Spot-check ===" if [ -f tsconfig.json ]; then echo "✅ tsconfig.json"; else echo "ℹ️ tsconfig.json (skipped for JS/MJS)"; fi for f in .gitignore biome.json .changeset/config.json .gitea/workflows/ci.yml .githooks/pre-push; do test -f "$f" && echo "✅ $f" || echo "⚠️ MISSING: $f" done node -e " const p = require('./package.json'); const required = ['build', 'check', 'test', 'test:ci', 'format', 'preinstall', 'prepublishOnly']; const missing = required.filter(s => !p.scripts?.[s]); if (missing.length) console.log('⚠️ Missing scripts:', missing.join(', ')); else console.log('✅ All scripts present'); if (!p.packageManager) console.log('⚠️ Missing packageManager'); else console.log('✅ packageManager:', p.packageManager); " ``` List any missing items as warnings but still commit what exists. 3. If no changes: set $status=no_changes 4. Stage all: `git add -A` 5. Commit: `git commit -m "chore: normalize to bun monorepo conventions"` 6. Push: `git push` Post-condition: Clean commit pushed, `git status` shows clean working tree. description: Commits all normalization changes frontmatter: oneOf: - properties: $status: const: committed commitHash: type: string repoPath: type: string - properties: $status: const: no_changes repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: [] workspace: goal: You set up the foundational bun workspace configuration for a monorepo. output: List what was changed. Set $status to done (workspace working) or failed (with reason). procedure: | cd into the repo path provided in your task prompt. IMPORTANT: Be idempotent — check before modifying. If something is already correct, skip it. Check and fix: 1. Root `package.json` must have `"workspaces": ["packages/*"]` 2. Root `package.json` must have `"private": true` 3. If packages exist in other locations (e.g. root src/, top-level dirs), migrate them under `packages/` 4. Each package under `packages/` must have its own `package.json` with `"name"` and `"type": "module"` 5. `.gitignore` must exist and include at minimum: ``` node_modules/ dist/ *.tsbuildinfo ``` If `.gitignore` is missing or doesn't cover these, append the missing entries (don't overwrite existing content). 6. If node_modules/ is already tracked in git, remove it: `git rm -r --cached node_modules/ */node_modules/ 2>/dev/null` 7. Run `bun install` to verify workspace resolution works ## Verification (must all pass) ```bash # 1. bun install works bun install # 2. gitignore covers essentials grep -q 'node_modules' .gitignore grep -q 'dist' .gitignore # 3. no node_modules tracked test -z "$(git ls-files | grep node_modules)" # 4. all packages have package.json for d in packages/*/; do test -f "$d/package.json" || echo "MISSING: $d/package.json"; done ``` Post-condition: All verification commands pass. description: Ensure bun workspace structure frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - workspace-setup guardrails: goal: You configure enforcement mechanisms that block npm/pnpm/yarn usage and direct npm publish. output: List what guardrails were installed. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. IMPORTANT: Be idempotent — check before adding. ## 1. Block wrong package manager Add to root `package.json` (if not already present): - `"packageManager": "bun@"` — use the version from `bun --version` - `"scripts.preinstall": "npx only-allow bun"` — blocks npm/pnpm/yarn install ## 2. Block direct npm publish Add to root `package.json` (if not already present): - `"scripts.prepublishOnly": "echo 'Use bun run release instead' && exit 1"` For each non-private package under `packages/`: - Add `"scripts.prepublishOnly": "echo 'Use bun run release from repo root' && exit 1"` to their package.json (if not present) If `scripts/publish-all.mjs` exists, verify it uses `--ignore-scripts` in the npm publish command. If it doesn't, add `--ignore-scripts` to the publish command. ## 3. Git hooks Create `.githooks/pre-push` (if not already present): ```bash #!/usr/bin/env bash set -euo pipefail echo "🔍 Running checks..." bun run check echo "🧪 Running tests..." bun run test echo "✅ All checks passed!" ``` Make it executable: `chmod +x .githooks/pre-push` Configure git to use hooks dir: `git config core.hooksPath .githooks` ## Verification ```bash # 1. packageManager field exists node -e "const p = require('./package.json'); if (!p.packageManager) { console.error('❌ missing packageManager'); process.exit(1); } console.log('✅ packageManager:', p.packageManager)" # 2. preinstall guard exists node -e "const p = require('./package.json'); if (!p.scripts?.preinstall?.includes('only-allow')) { console.error('❌ missing preinstall guard'); process.exit(1); } console.log('✅ preinstall guard')" # 3. npm install is blocked (with timeout to prevent hang) timeout 10 npm install 2>&1 | head -5 || true # 4. prepublishOnly exists node -e "const p = require('./package.json'); if (!p.scripts?.prepublishOnly) { console.error('❌ missing prepublishOnly'); process.exit(1); } console.log('✅ prepublishOnly guard')" # 5. publish script uses --ignore-scripts if [ -f scripts/publish-all.mjs ]; then grep -q 'ignore-scripts' scripts/publish-all.mjs && echo '✅ publish uses --ignore-scripts' || echo '❌ publish missing --ignore-scripts'; fi # 6. git hooks configured test -f .githooks/pre-push && echo '✅ pre-push hook' || echo '❌ missing pre-push hook' ``` Post-condition: All verification checks pass. description: Install project guardrails to prevent wrong package manager and publish workflow frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - guardrails typescript: goal: You configure TypeScript for a bun monorepo with composite project references. output: List what was configured. Set $status to done or failed. procedure: |- cd into the repo path from your task prompt. IMPORTANT: Be idempotent — if tsconfig.json already exists with correct settings, don't overwrite. ## Step 0: Detect if project needs TypeScript compilation ```bash TS_FILES=$(find packages/ -name '*.ts' -o -name '*.tsx' | grep -v node_modules | grep -v dist | grep -v '.d.ts' | head -5) ``` **If there are NO .ts/.tsx source files** (pure JS/MJS project): - Do NOT create root tsconfig.json - Do NOT add `bunx tsc --build` as the build script - Do NOT add typescript/bun-types to devDependencies unless already present - Preserve the existing `build` script (e.g. vite build, esbuild, etc.) - If no build script exists, add one based on the project's bundler (check for vite.config, esbuild, etc.) - Set $status=done with note "JS/MJS project — skipped TypeScript setup" - STOP HERE. **If .ts/.tsx files exist**, continue with full TypeScript setup: Check and fix: 1. Root `tsconfig.json` must exist with: - `"compilerOptions"`: target ES2022, module NodeNext, moduleResolution NodeNext, strict true, composite true, declaration true, declarationMap true, sourceMap true - `"references"`: array with `{ "path": "packages/" }` for each package - `"files": []` (root does not compile files directly) 2. Each package must have `tsconfig.json` with: - `"extends": "../../tsconfig.json"` (inherit root config) - `"compilerOptions": { "rootDir": "src", "outDir": "dist" }` - `"include": ["src"]` - `"references"` to sibling packages it depends on 3. Root scripts must include: `"build": "bunx tsc --build"`, `"typecheck": "bunx tsc --build"` 4. `devDependencies` at root: `typescript`, `bun-types`, `@types/node` ## Verification ```bash if [ ! -f tsconfig.json ]; then echo "JS/MJS project — no tsconfig needed" node -e "const p = require('./package.json'); if (!p.scripts?.build) { console.error('Missing build script'); process.exit(1); } console.log('build:', p.scripts.build)" bun run build exit 0 fi test -f tsconfig.json for d in packages/*/; do test -f "$d/tsconfig.json" || echo "MISSING: $d/tsconfig.json"; done bunx tsc --build ``` Post-condition: For TS projects — `bunx tsc --build` succeeds. For JS/MJS projects — `bun run build` succeeds. description: Configure TypeScript with project references frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - typescript-config package-metadata: goal: You ensure every package has consistent metadata for publishing and discoverability. output: List what was standardized per package. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. IMPORTANT: Be idempotent — skip fields that are already correctly set. For each package under `packages/`: 1. `"type": "module"` — must be set 2. `"files": ["src", "dist", "package.json"]` — for published packages (non-private) 3. `"publishConfig": { "access": "public" }` — for @scoped public packages (non-private) 4. `"repository"` — must point to the correct git remote and directory - Read remote URL: `git remote get-url origin` - Set `"directory": "packages/"` 5. `"exports"` — conditional exports for TypeScript packages: ```json ".": { "bun": "./src/index.ts", "types": "./dist/index.d.ts", "import": "./dist/index.js" } ``` Skip if package has no `src/index.ts`. 6. Private packages (server, frontend, tools) must have `"private": true` and can skip publishConfig/files 7. Each package should have a `"scripts"` section with at least `"test"` if tests exist 8. Workspace dependencies should use `"workspace:^"` protocol, not version numbers - Check: `grep -r '"@uncaged/' packages/*/package.json | grep -v 'workspace:'` ## Verification ```bash # Check every non-private package has required fields for d in packages/*/; do pkg="$d/package.json" test -f "$pkg" || continue node -e " const p = require('./$pkg'); if (p.private) { console.log('$d: private, skip'); process.exit(0); } const issues = []; if (p.type !== 'module') issues.push('missing type:module'); if (!p.exports) issues.push('missing exports'); if (!p.publishConfig) issues.push('missing publishConfig'); if (!p.files) issues.push('missing files'); if (issues.length) console.log('$d:', issues.join(', ')); else console.log('$d: OK'); " done # No hardcoded workspace deps ! grep -r '"@uncaged/' packages/*/package.json | grep -v 'workspace:' | grep -v node_modules ``` Post-condition: Verification script shows OK for all non-private packages, no hardcoded workspace versions. description: Standardize package.json metadata across all packages frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - package-config solve-issue-workflow: goal: You set up the solve-issue workflow YAML so the project can use `uwf` for issue resolution. output: Describe the workflow registered. Set $status to done or failed. procedure: | cd into the repo path from your task prompt. 1. Check if `uwf` CLI is available: `which uwf` - If not available: set $status=failed, reason="uwf CLI not installed" 2. Create `workflows/solve-issue.yaml` adapted for this project: - Copy the standard solve-issue workflow structure (planner → developer → reviewer → tester → committer) - Adjust the developer role procedure to use this project's test runner and build commands - The workflow should reference the correct repo path and build toolchain (bun) 3. Register with uwf: `uwf workflow add workflows/solve-issue.yaml` ## Verification ```bash # 1. workflow file exists test -f workflows/solve-issue.yaml # 2. registered in uwf uwf workflow list --format json | node -e " let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{ const wfs = JSON.parse(d); if (wfs.some(w => w.name === 'solve-issue')) console.log('✅ solve-issue registered'); else { console.error('❌ solve-issue not found'); process.exit(1); } }) " ``` Post-condition: `uwf workflow list` includes solve-issue. description: Register solve-issue workflow for the project frontmatter: oneOf: - properties: $status: const: done repoPath: type: string - properties: $status: const: failed reason: type: string repoPath: type: string capabilities: - workflow-config description: Normalize an existing project to @uncaged bun monorepo conventions. Supports both TypeScript and JS/MJS projects. Each role handles one configuration layer. All roles allow fail.