diff --git a/.async-pipeline/tasks.lock.json b/.async-pipeline/tasks.lock.json new file mode 100644 index 0000000..2a502ee --- /dev/null +++ b/.async-pipeline/tasks.lock.json @@ -0,0 +1,195 @@ +{ + "version": 1, + "generator": "@async/pipeline", + "config": "pipeline.ts", + "prefix": "pipeline", + "runners": [ + "package" + ], + "targets": [ + { + "package": "@async/auto-git" + } + ], + "manifests": [ + { + "path": "package.json", + "runner": "package", + "field": "scripts", + "commands": [ + { + "name": "pipeline:pages", + "value": "async-pipeline run pages" + }, + { + "name": "pipeline:preview", + "value": "async-pipeline run preview" + }, + { + "name": "pipeline:publish", + "value": "async-pipeline run publish" + }, + { + "name": "pipeline:publish-gists", + "value": "async-pipeline run publish-gists" + }, + { + "name": "pipeline:publish-github", + "value": "async-pipeline run publish-github" + }, + { + "name": "pipeline:release-doctor", + "value": "async-pipeline run release-doctor" + }, + { + "name": "pipeline:snapshot", + "value": "async-pipeline run snapshot" + }, + { + "name": "pipeline:verify", + "value": "async-pipeline run verify" + }, + { + "name": "pipeline:api-surface", + "value": "async-pipeline run-task api-surface" + }, + { + "name": "pipeline:api-surface:generate", + "value": "async-pipeline run-task api-surface-generate" + }, + { + "name": "pipeline:github:check", + "value": "async-pipeline github check" + }, + { + "name": "pipeline:github:generate", + "value": "async-pipeline github generate" + }, + { + "name": "pipeline:pack", + "value": "async-pipeline run-task pack" + }, + { + "name": "pipeline:publish:github:main", + "value": "async-pipeline publish github main --package ." + }, + { + "name": "pipeline:publish:github:pr", + "value": "async-pipeline publish github pr --package ." + }, + { + "name": "pipeline:publish:github:release", + "value": "async-pipeline publish github release --package ." + }, + { + "name": "pipeline:publish:npm", + "value": "async-pipeline publish npm --package ." + }, + { + "name": "pipeline:release:doctor", + "value": "async-pipeline release doctor --package ." + }, + { + "name": "pipeline:release:ensure", + "value": "async-pipeline release ensure --package ." + }, + { + "name": "pipeline:sync:check", + "value": "async-pipeline sync check" + }, + { + "name": "pipeline:sync:generate", + "value": "async-pipeline sync generate" + } + ] + } + ], + "commands": [ + { + "name": "pipeline:pages", + "value": "async-pipeline run pages" + }, + { + "name": "pipeline:preview", + "value": "async-pipeline run preview" + }, + { + "name": "pipeline:publish", + "value": "async-pipeline run publish" + }, + { + "name": "pipeline:publish-gists", + "value": "async-pipeline run publish-gists" + }, + { + "name": "pipeline:publish-github", + "value": "async-pipeline run publish-github" + }, + { + "name": "pipeline:release-doctor", + "value": "async-pipeline run release-doctor" + }, + { + "name": "pipeline:snapshot", + "value": "async-pipeline run snapshot" + }, + { + "name": "pipeline:verify", + "value": "async-pipeline run verify" + }, + { + "name": "pipeline:api-surface", + "value": "async-pipeline run-task api-surface" + }, + { + "name": "pipeline:api-surface:generate", + "value": "async-pipeline run-task api-surface-generate" + }, + { + "name": "pipeline:github:check", + "value": "async-pipeline github check" + }, + { + "name": "pipeline:github:generate", + "value": "async-pipeline github generate" + }, + { + "name": "pipeline:pack", + "value": "async-pipeline run-task pack" + }, + { + "name": "pipeline:publish:github:main", + "value": "async-pipeline publish github main --package ." + }, + { + "name": "pipeline:publish:github:pr", + "value": "async-pipeline publish github pr --package ." + }, + { + "name": "pipeline:publish:github:release", + "value": "async-pipeline publish github release --package ." + }, + { + "name": "pipeline:publish:npm", + "value": "async-pipeline publish npm --package ." + }, + { + "name": "pipeline:release:doctor", + "value": "async-pipeline release doctor --package ." + }, + { + "name": "pipeline:release:ensure", + "value": "async-pipeline release ensure --package ." + }, + { + "name": "pipeline:sync:check", + "value": "async-pipeline sync check" + }, + { + "name": "pipeline:sync:generate", + "value": "async-pipeline sync generate" + } + ], + "hash": "sha256:0b27bde1516b30d4a3d52ba5a914f38499df30832cf2c348ed09fa682a37d10e", + "generatedAt": "2026-06-14T10:42:44.629Z" +} diff --git a/.github/async-pipeline.lock.json b/.github/async-pipeline.lock.json index 0278f1c..84824d7 100644 --- a/.github/async-pipeline.lock.json +++ b/.github/async-pipeline.lock.json @@ -3,8 +3,8 @@ "generator": "@async/pipeline", "config": "pipeline.ts", "workflow": ".github/workflows/async-pipeline.yml", - "hash": "sha256:e6b6c9017c15bda1ffd6bec73631641972729e68caf6857e542f9ef1c72293ab", - "generatedAt": "2026-06-14T02:12:26.378Z", + "hash": "sha256:6269d1663875dffb1e9d956fa2b75f2e60e21cdd8d1fbf44ab6eff5da03c69b2", + "generatedAt": "2026-06-14T10:42:44.627Z", "triggers": { "pull_request": {}, "push": { @@ -17,14 +17,59 @@ }, "jobs": [ { - "id": "publish", + "id": "pages", "target": [ - "publish-npm" + "docs" ], "trigger": [ - "release", + "pr", + "main", "manual" ], + "env": {}, + "github": { + "pages": { + "build": { + "kind": "jekyll", + "source": "./docs", + "destination": "./_site" + } + } + }, + "if": "(github.event_name == 'pull_request') || (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'pages')" + }, + { + "id": "preview", + "target": [ + "preview" + ], + "trigger": [ + "pr" + ], + "env": { + "GITHUB_TOKEN": { + "kind": "async-pipeline.env.secret", + "name": "GITHUB_TOKEN" + } + }, + "github": { + "permissions": { + "issues": "write", + "packages": "write", + "pullRequests": "write" + } + }, + "if": "github.event_name == 'pull_request'" + }, + { + "id": "publish", + "target": [ + "publish" + ], + "trigger": [ + "manual", + "release" + ], "env": { "GITHUB_TOKEN": { "kind": "async-pipeline.env.secret", @@ -44,10 +89,11 @@ }, "github": { "permissions": { + "contents": "read", "packages": "write" } }, - "if": "(github.event_name == 'release') || (github.event_name == 'workflow_dispatch')" + "if": "(github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'publish') || (github.event_name == 'release')" }, { "id": "publish-gists", @@ -64,7 +110,29 @@ "name": "GIST_TOKEN" } }, - "if": "(github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch')" + "if": "(github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'publish-gists')" + }, + { + "id": "publish-github", + "target": [ + "publish-github" + ], + "trigger": [ + "manual" + ], + "env": { + "GITHUB_TOKEN": { + "kind": "async-pipeline.env.secret", + "name": "GITHUB_TOKEN" + } + }, + "github": { + "permissions": { + "contents": "write", + "packages": "write" + } + }, + "if": "github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'publish-github'" }, { "id": "release-doctor", @@ -84,12 +152,26 @@ "name": "NPM_TOKEN" } }, - "environment": { - "name": "npm-publish", - "url": "https://www.npmjs.com/package/@async/auto-git" + "github": { + "permissions": { + "packages": "write" + } }, - "requires": { - "provenance": true + "if": "github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'release-doctor'" + }, + { + "id": "snapshot", + "target": [ + "snapshot" + ], + "trigger": [ + "main" + ], + "env": { + "GITHUB_TOKEN": { + "kind": "async-pipeline.env.secret", + "name": "GITHUB_TOKEN" + } }, "github": { "permissions": { @@ -97,25 +179,32 @@ "packages": "write" } }, - "if": "github.event_name == 'workflow_dispatch'" + "if": "github.event_name == 'push' && (github.ref == 'refs/heads/main')" }, { "id": "verify", "target": [ - "test", - "check-github" + "pack" ], "trigger": [ "pr", "main", - "manual", - "release" + "release", + "manual" ], "env": {}, - "if": "(github.event_name == 'pull_request') || (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch') || (github.event_name == 'release')" + "if": "(github.event_name == 'pull_request') || (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'release') || (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'verify')" } ], "packageManager": "pnpm", "nodeVersion": "24", - "taskCache": true + "taskCache": true, + "manualDispatchJobs": [ + "pages", + "publish", + "publish-gists", + "publish-github", + "release-doctor", + "verify" + ] } diff --git a/.github/workflows/async-pipeline.yml b/.github/workflows/async-pipeline.yml index dee7aae..9fe2ba8 100644 --- a/.github/workflows/async-pipeline.yml +++ b/.github/workflows/async-pipeline.yml @@ -10,14 +10,140 @@ on: - "main" release: workflow_dispatch: + inputs: + job: + description: "Pipeline job to run" + required: true + type: choice + options: + - "pages" + - "publish" + - "publish-gists" + - "publish-github" + - "release-doctor" + - "verify" permissions: contents: read jobs: + pages: + name: pages + if: (github.event_name == 'pull_request') || (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'pages') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Restore task cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .async/cache + key: async-pipeline-${{ runner.os }}-${{ github.sha }} + restore-keys: | + async-pipeline-${{ runner.os }}- + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + package-manager-cache: false + + - name: Enable pnpm + run: | + corepack enable + corepack prepare pnpm@10.20.0 --activate + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check generated workflow + run: pnpm async-pipeline github check + + - name: Run pipeline job + run: pnpm async-pipeline run pages + env: + CI: true + + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: "./docs" + destination: "./_site" + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: "./_site" + + pages-deploy: + name: pages-deploy + needs: "pages" + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + environment: + name: "github-pages" + url: "${{ steps.deployment.outputs.page_url }}" + permissions: + pages: write + id-token: write + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + preview: + name: preview + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + packages: write + pull-requests: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Restore task cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .async/cache + key: async-pipeline-${{ runner.os }}-${{ github.sha }} + restore-keys: | + async-pipeline-${{ runner.os }}- + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + package-manager-cache: false + + - name: Enable pnpm + run: | + corepack enable + corepack prepare pnpm@10.20.0 --activate + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check generated workflow + run: pnpm async-pipeline github check + + - name: Run pipeline job + run: pnpm async-pipeline run preview + env: + CI: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + publish: name: publish - if: (github.event_name == 'release') || (github.event_name == 'workflow_dispatch') + if: (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'publish') || (github.event_name == 'release') runs-on: ubuntu-latest environment: name: "npm-publish" @@ -68,7 +194,7 @@ jobs: publish-gists: name: publish-gists - if: (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch') + if: (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'publish-gists') runs-on: ubuntu-latest steps: - name: Checkout @@ -106,16 +232,12 @@ jobs: CI: true GIST_TOKEN: ${{ secrets.GIST_TOKEN }} - release-doctor: - name: release-doctor - if: github.event_name == 'workflow_dispatch' + publish-github: + name: publish-github + if: github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'publish-github' runs-on: ubuntu-latest - environment: - name: "npm-publish" - url: "https://www.npmjs.com/package/@async/auto-git" permissions: contents: write - id-token: write packages: write steps: - name: Checkout @@ -136,8 +258,48 @@ jobs: registry-url: https://registry.npmjs.org/ package-manager-cache: false - - name: Use current npm - run: npm install -g npm@11.16.0 + - name: Enable pnpm + run: | + corepack enable + corepack prepare pnpm@10.20.0 --activate + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check generated workflow + run: pnpm async-pipeline github check + + - name: Run pipeline job + run: pnpm async-pipeline run publish-github + env: + CI: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release-doctor: + name: release-doctor + if: github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'release-doctor' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Restore task cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .async/cache + key: async-pipeline-${{ runner.os }}-${{ github.sha }} + restore-keys: | + async-pipeline-${{ runner.os }}- + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + package-manager-cache: false - name: Enable pnpm run: | @@ -157,9 +319,52 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + snapshot: + name: snapshot + if: github.event_name == 'push' && (github.ref == 'refs/heads/main') + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Restore task cache + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: .async/cache + key: async-pipeline-${{ runner.os }}-${{ github.sha }} + restore-keys: | + async-pipeline-${{ runner.os }}- + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + package-manager-cache: false + + - name: Enable pnpm + run: | + corepack enable + corepack prepare pnpm@10.20.0 --activate + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check generated workflow + run: pnpm async-pipeline github check + + - name: Run pipeline job + run: pnpm async-pipeline run snapshot + env: + CI: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + verify: name: verify - if: (github.event_name == 'pull_request') || (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'workflow_dispatch') || (github.event_name == 'release') + if: (github.event_name == 'pull_request') || (github.event_name == 'push' && (github.ref == 'refs/heads/main')) || (github.event_name == 'release') || (github.event_name == 'workflow_dispatch' && github.event.inputs.job == 'verify') runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.gitignore b/.gitignore index dd23f0a..f55d359 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .async/ .DS_Store +dist/ +_site/ .tmp/ node_modules/ *.tgz diff --git a/.npmrc b/.npmrc index 302bf0a..f002292 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -minimum-release-age-exclude[]=@async/pipeline +minimum-release-age-exclude[]=@async/* diff --git a/AGENTS.md b/AGENTS.md index 53c053f..cf9b154 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,12 +4,12 @@ This repo is the source of truth for the Auto Git skill suite. Do not edit gener ## Definition of done -- Run `pnpm verify`. +- Run `pnpm run verify`. - Keep `skills/**` and `docs/gists/**` as the source files. -- Regenerate `gists/**` with `pnpm gists:package` after source edits. -- Keep the generated gist package check passing with `pnpm gists:check`. +- Regenerate `gists/**` with `pnpm run gists:package` after source edits. +- Keep the generated gist package check passing with `pnpm run gists:check`. - Keep GitHub Actions generated by `@async/pipeline`; do not hand-edit `.github/workflows/async-pipeline.yml`. -- If `pipeline.ts` changes, run `pnpm pipeline:github:generate` and `pnpm pipeline:github:check`. +- If `pipeline.ts` changes, run `pnpm run pipeline:github:generate` and `pnpm run pipeline:github:check`. - Never commit secrets or local token files. Gist publishing uses the `GIST_TOKEN` environment variable locally and the `GIST_TOKEN` GitHub Actions secret in CI. ## Commit style diff --git a/API_SURFACE.md b/API_SURFACE.md new file mode 100644 index 0000000..31aee4d --- /dev/null +++ b/API_SURFACE.md @@ -0,0 +1,85 @@ +# @async/auto-git API Surface Ledger + +This file is the generated review ledger for semantic API contract features. It is current-state contract documentation, not a changelog or tutorial. + +## Auto Git CLI + +Contract: `@async/auto-git.cli` + +### Bins + +| Feature | Title | Release | Stability | Lifecycle | Replacement | Docs | +| --- | --- | --- | --- | --- | --- | --- | +| `bin.auto-git` | auto-git dispatcher | public | stable | active | | | +| `bin.auto-git-finish` | Auto Git finish helper | public | stable | active | | | +| `bin.auto-git-gate` | Auto Git verification gate helper | public | stable | active | | | +| `bin.auto-git-ledger` | Auto Git ledger inspection helper | public | stable | active | | | +| `bin.auto-git-release-doctor` | Auto Git release doctor helper | public | stable | active | | | +| `bin.auto-git-release-preflight` | Auto Git release tag preflight helper | public | stable | active | | | +| `bin.auto-git-snapshot` | Auto Git topology snapshot helper | public | stable | active | | | +| `bin.auto-git-start` | Auto Git start helper | public | stable | active | | | + +## Auto Git Skill Packages + +Contract: `@async/auto-git.skills` + +### Gists + +| Feature | Title | Release | Stability | Lifecycle | Replacement | Docs | +| --- | --- | --- | --- | --- | --- | --- | +| `gist.auto-git` | Generated Auto Git gist package | public | generated | active | | | +| `gist.git-history-rewrite` | Generated Git History Rewrite gist package | public | generated | active | | | +| `gist.git-intent-audit` | Generated Git Intent Audit gist package | public | generated | active | | | + +### Skills + +| Feature | Title | Release | Stability | Lifecycle | Replacement | Docs | +| --- | --- | --- | --- | --- | --- | --- | +| `skill.auto-git` | Auto Git Codex skill | public | stable | active | | | +| `skill.git-history-rewrite` | Git History Rewrite Codex skill | public | stable | active | | | +| `skill.git-intent-audit` | Git Intent Audit Codex skill | public | stable | active | | | + +## Auto Git Pipeline Lifecycle + +Contract: `@async/auto-git.lifecycle` + +### Jobs + +| Feature | Title | Release | Stability | Lifecycle | Replacement | Docs | +| --- | --- | --- | --- | --- | --- | --- | +| `job.pages` | Generated GitHub Pages job | beta | preview | active | | | +| `job.preview` | PR preview package job | beta | preview | active | | | +| `job.publish` | npm publish job | beta | preview | active | | | +| `job.publish-gists` | Gist publishing job | beta | preview | active | | | +| `job.publish-github` | Stable GitHub Packages mirror job | beta | preview | active | | | +| `job.release-doctor` | Release doctor job | beta | preview | active | | | +| `job.snapshot` | Main snapshot package job | beta | preview | active | | | +| `job.verify` | Verification job | public | stable | active | | | + +## Auto Git Package Metadata + +Contract: `@async/auto-git.package` + +### Metadata + +| Feature | Title | Release | Stability | Lifecycle | Replacement | Docs | +| --- | --- | --- | --- | --- | --- | --- | +| `package.api-ledger` | Published API surface ledger files | public | stable | active | | | +| `package.dist-runtime` | Generated dist runtime included in package files | public | generated | active | | | +| `package.public-access` | Public npm package publish configuration | public | stable | active | | | +| `package.type-module` | Node ESM package boundary | public | stable | active | | | + +## Supported Surfaces + +| Contract | Hash | Features | +| --- | --- | --- | +| `@async/auto-git.cli` | `sha256:ac8a756ea253985ee47ae8dc6ddb6eb2fd52c1e625d2147fe29fe0e079c9ac38` | `bin.auto-git`, `bin.auto-git-finish`, `bin.auto-git-gate`, `bin.auto-git-ledger`, `bin.auto-git-release-doctor`, `bin.auto-git-release-preflight`, `bin.auto-git-snapshot`, `bin.auto-git-start` | +| `@async/auto-git.lifecycle` | `sha256:3d4a35779e98a3f64364ad07095f03bfb08dd9e963c83681072deeda89946864` | `job.pages`, `job.preview`, `job.publish`, `job.publish-gists`, `job.publish-github`, `job.release-doctor`, `job.snapshot`, `job.verify` | +| `@async/auto-git.package` | `sha256:720f660ab5ee82ad83ef69f2677593a4d82c4a0a23082de09148edd4d9e4f480` | `package.api-ledger`, `package.dist-runtime`, `package.public-access`, `package.type-module` | +| `@async/auto-git.skills` | `sha256:cd65d953a9808ba1222531e02ce66575d05126c6714b7bcfce1294eb4601068e` | `gist.auto-git`, `gist.git-history-rewrite`, `gist.git-intent-audit`, `skill.auto-git`, `skill.git-history-rewrite`, `skill.git-intent-audit` | + +## Required Surfaces + +| Contract | Hash | Features | +| --- | --- | --- | +| `@async/pipeline.cli` | `sha256:d98fbabdc807d0a093266381164ba0442c8fe65c172b9fc7009280f91b236e8e` | `cli.github.check`, `cli.github.generate`, `cli.publish.github`, `cli.publish.npm`, `cli.release.doctor`, `cli.run`, `cli.run-task`, `cli.sync.check`, `cli.sync.generate` | diff --git a/CHANGELOG.md b/CHANGELOG.md index e99dc45..478428e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.3.0 - 2026-06-14 + +- Move release, preview, snapshot, GitHub Pages, and API surface checks onto the generated `@async/pipeline` workflow. +- Replace repo-specific publish scripts with shared pipeline lifecycle commands. +- Add generated docs-site checks and keep packaged gist docs aligned with pnpm task examples. + ## 0.2.2 - 2026-06-14 - Publish npm-safe Auto Git CLI bin wrappers so package installs expose the helper commands. diff --git a/README.md b/README.md index 0d9f92d..74c9231 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ gists/ Generated flat gist packages scripts/ Validation, packaging, and publishing tools tests/ Node test suite for package invariants pipeline.ts @async/pipeline workflow definition +api-contract.json Semantic API surface manifest +API_SURFACE.md Generated API surface ledger ``` The commit intent rules live in `skills/auto-git/references/commit-by-intent.md`. @@ -74,16 +76,20 @@ the completed branch and head for later handoff. Do not edit `gists/**` by hand. Update `skills/**` or `docs/gists/**`, then run: ```sh -pnpm gists:package +pnpm run gists:package ``` +That command is the source-to-generated implementation step for gist packages. +Normal verification and publishing still route through the pipeline-owned +scripts below. + ## Helper CLI The npm package exposes an `auto-git` dispatcher plus individual helper bins: ```sh auto-git snapshot --cwd "$PWD" --write-state -auto-git gate --cwd "$PWD" --profile auto -- pnpm verify +auto-git gate --cwd "$PWD" --profile auto -- pnpm run pipeline:verify auto-git release-preflight --cwd "$PWD" --require-verification ``` @@ -97,46 +103,45 @@ when the npm CLI is not on `PATH`. Release commits should use the `release(...)` intent. Keep the `package.json` version bump, the matching `CHANGELOG.md` section, and any lockfile or package -metadata caused by the bump in the same release commit. `pnpm verify` checks +metadata caused by the bump in the same release commit. `pnpm run pipeline:verify` checks that the current package version has a changelog entry. -Before creating or pushing a release tag, run the full release gate and the -publish-path preflight on the exact commit that will be tagged. Push the branch -before the tag. If a remote release tag already needs to move, stop for explicit -approval and use only a lease-protected tag update. +Before release, run the full release gate on the exact commit that will be +published. Push and merge the release-prep branch before using the generated +workflow. If a remote release tag already exists and does not match the release +commit, stop for explicit approval; do not move it from the local machine. Published releases have four public surfaces that must agree: the git tag, GitHub Release, npm package, and GitHub Packages mirror. Normal releases use the generated `@async/pipeline` workflow with npm provenance: ```sh -pnpm release:check -git tag vX.Y.Z -git push origin main vX.Y.Z -gh release create vX.Y.Z --verify-tag --generate-notes --title vX.Y.Z -pnpm release:doctor +pnpm run pipeline:verify -- --force +# In GitHub Actions, run "Async Pipeline" with the publish job selected. +pnpm run pipeline:release:doctor ``` -The release workflow publishes the GitHub Packages mirror first and then npm. -`pnpm release:doctor` diagnoses missing or drifted tag/npm/GitHub state and -names only repairs that are safe to run. +The generated workflow creates or verifies the tag and GitHub Release, publishes +the GitHub Packages mirror first, then publishes npm. +`pnpm run pipeline:release:doctor` diagnoses missing or drifted tag/npm/GitHub +state and names only repairs that are safe to run. ## Local Verification ```sh pnpm install -pnpm verify +pnpm run pipeline:verify ``` Useful focused commands: ```sh -pnpm skills:validate -pnpm gists:check -pnpm test -pnpm pipeline:github:check -pnpm release:pack -pnpm release:doctor +pnpm run pipeline:api-surface +pnpm run pipeline:pack +pnpm run pipeline:pages +pnpm run pipeline:sync:check +pnpm run pipeline:github:check +pnpm run pipeline:release:doctor ``` ## Gist Publishing @@ -150,16 +155,22 @@ The published gist packages are generated from this repo: Publishing requires a token that can update those gists: ```sh -GIST_TOKEN=... pnpm gists:publish +GIST_TOKEN=... pnpm run pipeline:publish-gists ``` -On GitHub Actions, configure a repository secret named `GIST_TOKEN`. The generated `@async/pipeline` workflow publishes gists on pushes to `main` and on manual dispatch. +On GitHub Actions, configure a repository secret named `GIST_TOKEN`. The +generated `@async/pipeline` workflow publishes gists on pushes to `main`; manual +publishing uses the generated workflow dispatch `job` selector with +`publish-gists`. + +The lower-level `gists:*` scripts are implementation commands used by pipeline +tasks and source-to-generated packaging checks. ## Pipeline `@async/pipeline` owns the GitHub Actions workflow. Regenerate after editing `pipeline.ts`: ```sh -pnpm pipeline:github:generate -pnpm pipeline:github:check +pnpm run pipeline:sync:generate +pnpm run pipeline:sync:check ``` diff --git a/api-contract.json b/api-contract.json new file mode 100644 index 0000000..f3bf796 --- /dev/null +++ b/api-contract.json @@ -0,0 +1,311 @@ +{ + "format": "api-contract.package.v1", + "packageName": "@async/auto-git", + "catalogs": [ + { + "format": "api-contract.catalog.v1", + "contractId": "@async/auto-git.cli", + "title": "Auto Git CLI", + "features": [ + { + "id": "bin.auto-git", + "title": "auto-git dispatcher", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-finish", + "title": "Auto Git finish helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-gate", + "title": "Auto Git verification gate helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-ledger", + "title": "Auto Git ledger inspection helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-release-doctor", + "title": "Auto Git release doctor helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-release-preflight", + "title": "Auto Git release tag preflight helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-snapshot", + "title": "Auto Git topology snapshot helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + }, + { + "id": "bin.auto-git-start", + "title": "Auto Git start helper", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "bins" + } + ] + }, + { + "format": "api-contract.catalog.v1", + "contractId": "@async/auto-git.skills", + "title": "Auto Git Skill Packages", + "features": [ + { + "id": "gist.auto-git", + "title": "Generated Auto Git gist package", + "releaseTag": "public", + "stability": "generated", + "lifecycle": "active", + "group": "gists" + }, + { + "id": "gist.git-history-rewrite", + "title": "Generated Git History Rewrite gist package", + "releaseTag": "public", + "stability": "generated", + "lifecycle": "active", + "group": "gists" + }, + { + "id": "gist.git-intent-audit", + "title": "Generated Git Intent Audit gist package", + "releaseTag": "public", + "stability": "generated", + "lifecycle": "active", + "group": "gists" + }, + { + "id": "skill.auto-git", + "title": "Auto Git Codex skill", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "skills" + }, + { + "id": "skill.git-history-rewrite", + "title": "Git History Rewrite Codex skill", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "skills" + }, + { + "id": "skill.git-intent-audit", + "title": "Git Intent Audit Codex skill", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "skills" + } + ] + }, + { + "format": "api-contract.catalog.v1", + "contractId": "@async/auto-git.lifecycle", + "title": "Auto Git Pipeline Lifecycle", + "features": [ + { + "id": "job.pages", + "title": "Generated GitHub Pages job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.preview", + "title": "PR preview package job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.publish", + "title": "npm publish job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.publish-gists", + "title": "Gist publishing job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.publish-github", + "title": "Stable GitHub Packages mirror job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.release-doctor", + "title": "Release doctor job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.snapshot", + "title": "Main snapshot package job", + "releaseTag": "beta", + "stability": "preview", + "lifecycle": "active", + "group": "jobs" + }, + { + "id": "job.verify", + "title": "Verification job", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "jobs" + } + ] + }, + { + "format": "api-contract.catalog.v1", + "contractId": "@async/auto-git.package", + "title": "Auto Git Package Metadata", + "features": [ + { + "id": "package.api-ledger", + "title": "Published API surface ledger files", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "metadata" + }, + { + "id": "package.dist-runtime", + "title": "Generated dist runtime included in package files", + "releaseTag": "public", + "stability": "generated", + "lifecycle": "active", + "group": "metadata" + }, + { + "id": "package.public-access", + "title": "Public npm package publish configuration", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "metadata" + }, + { + "id": "package.type-module", + "title": "Node ESM package boundary", + "releaseTag": "public", + "stability": "stable", + "lifecycle": "active", + "group": "metadata" + } + ] + } + ], + "supported": [ + { + "format": "api-contract.surface.v1", + "contractId": "@async/auto-git.cli", + "features": [ + "bin.auto-git", + "bin.auto-git-finish", + "bin.auto-git-gate", + "bin.auto-git-ledger", + "bin.auto-git-release-doctor", + "bin.auto-git-release-preflight", + "bin.auto-git-snapshot", + "bin.auto-git-start" + ] + }, + { + "format": "api-contract.surface.v1", + "contractId": "@async/auto-git.skills", + "features": [ + "gist.auto-git", + "gist.git-history-rewrite", + "gist.git-intent-audit", + "skill.auto-git", + "skill.git-history-rewrite", + "skill.git-intent-audit" + ] + }, + { + "format": "api-contract.surface.v1", + "contractId": "@async/auto-git.lifecycle", + "features": [ + "job.pages", + "job.preview", + "job.publish", + "job.publish-gists", + "job.publish-github", + "job.release-doctor", + "job.snapshot", + "job.verify" + ] + }, + { + "format": "api-contract.surface.v1", + "contractId": "@async/auto-git.package", + "features": [ + "package.api-ledger", + "package.dist-runtime", + "package.public-access", + "package.type-module" + ] + } + ], + "required": [ + { + "format": "api-contract.surface.v1", + "contractId": "@async/pipeline.cli", + "features": [ + "cli.github.check", + "cli.github.generate", + "cli.publish.github", + "cli.publish.npm", + "cli.release.doctor", + "cli.run", + "cli.run-task", + "cli.sync.check", + "cli.sync.generate" + ] + } + ] +} diff --git a/docs/gists/auto-git.md b/docs/gists/auto-git.md index 1d4fc43..36fa343 100644 --- a/docs/gists/auto-git.md +++ b/docs/gists/auto-git.md @@ -323,7 +323,7 @@ ledger so later chats can find the exact handoff. For long or environment-sensitive gates, use: ```bash -auto-git gate --cwd "$PWD" --profile auto --quiet-seconds 60 -- pnpm verify +auto-git gate --cwd "$PWD" --profile auto --quiet-seconds 60 -- pnpm run pipeline:verify ``` The gate helper records only safe metadata: command argv, profile, generated @@ -338,7 +338,7 @@ For Codex, place the files under your skills directory using the layout above. I For shell usage outside the copied skill directory, install the package: ```bash -npm install -g @async/auto-git +pnpm add -g @async/auto-git ``` After copying, validate with the local skill validator if available: diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..fc0dbf5 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,26 @@ +# Auto Git + +Auto Git is the source repo for the Codex skill suite that helps agents turn repository work into reviewable Git history. + +## Skills + +- `auto-git`: topology detection, commit-by-intent guidance, verification gates, coordinated branches, and release handoff checks. +- `git-intent-audit`: read-only evidence for large or unclear dirty worktrees. +- `git-history-rewrite`: audit-backed local history replay by change intent. + +## Maintenance Commands + +```sh +pnpm run pipeline:verify +pnpm run pipeline:pages +pnpm run pipeline:preview +pnpm run pipeline:snapshot +pnpm run pipeline:publish-gists +pnpm run pipeline:release-doctor +``` + +Package consumers can install the CLI from the npm registry: + +```sh +pnpm add -g @async/auto-git +``` diff --git a/gists/auto-git/README.md b/gists/auto-git/README.md index 1d4fc43..36fa343 100644 --- a/gists/auto-git/README.md +++ b/gists/auto-git/README.md @@ -323,7 +323,7 @@ ledger so later chats can find the exact handoff. For long or environment-sensitive gates, use: ```bash -auto-git gate --cwd "$PWD" --profile auto --quiet-seconds 60 -- pnpm verify +auto-git gate --cwd "$PWD" --profile auto --quiet-seconds 60 -- pnpm run pipeline:verify ``` The gate helper records only safe metadata: command argv, profile, generated @@ -338,7 +338,7 @@ For Codex, place the files under your skills directory using the layout above. I For shell usage outside the copied skill directory, install the package: ```bash -npm install -g @async/auto-git +pnpm add -g @async/auto-git ``` After copying, validate with the local skill validator if available: diff --git a/gists/auto-git/auto-git.reference-git-topology-lifecycles.md b/gists/auto-git/auto-git.reference-git-topology-lifecycles.md index c095715..79f7594 100644 --- a/gists/auto-git/auto-git.reference-git-topology-lifecycles.md +++ b/gists/auto-git/auto-git.reference-git-topology-lifecycles.md @@ -79,7 +79,7 @@ detect stale chats only when those chats used Auto Git and wrote ledger state. - If the snapshot recommends `executionProfile: loopback-capable`, start with that profile for the expensive gate instead of first running a doomed restricted command. - Prefer `auto-git gate --cwd "$PWD" --profile auto --quiet-seconds 60 -- [args...]` for long or environment-sensitive verification. It records the PID/process group, duration, failure class, and quiet process-tree diagnostics. - If npm/pnpm verification fails on HOME cache/log/config writes in a sandbox, retry the same repo-native command with `NO_UPDATE_NOTIFIER=1`, `NPM_CONFIG_CACHE=/private/tmp/-npm-cache`, and `NPM_CONFIG_LOGS_DIR=/private/tmp/-npm-logs`. -- If the repo is `async-pipeline`, full `pnpm release:check` should use the loopback-capable profile plus tmp npm cache/log dirs because some tests bind `127.0.0.1` and the release check can spawn npm pack. +- If the repo is `async-pipeline`, full `pnpm run release:check` should use the loopback-capable profile plus tmp npm cache/log dirs because some tests bind `127.0.0.1` and the release check can spawn npm pack. ## Lifecycle Mode Selection diff --git a/gists/auto-git/auto-git.script-auto-git-snapshot.mjs b/gists/auto-git/auto-git.script-auto-git-snapshot.mjs index 7407a8c..cf7499b 100644 --- a/gists/auto-git/auto-git.script-auto-git-snapshot.mjs +++ b/gists/auto-git/auto-git.script-auto-git-snapshot.mjs @@ -594,7 +594,7 @@ function packageManagerHints(repoRoot, packageJson, asyncRunLocks) { hints.push({ id: "async-pipeline-release-check", appliesWhen: ["running full async-pipeline release gate from Codex"], - commandName: "pnpm release:check", + commandName: "pnpm run release:check", retryEnv, executionProfile: "loopback-capable", startWithPlainCommand: false, @@ -609,8 +609,8 @@ function buildExecutionPlan(repoRoot, packageJson, asyncRunLocks, gitIndexWrite) const asyncPipeline = isAsyncPipelineRepo(repoRoot, packageJson); const verification = asyncPipeline ? { - name: "pnpm release:check", - command: ["pnpm", "release:check"], + name: "pnpm run release:check", + command: ["pnpm", "run", "release:check"], executionProfile: "loopback-capable", env: retryEnv, reason: "async-pipeline release checks may bind loopback and spawn npm pack" @@ -618,7 +618,7 @@ function buildExecutionPlan(repoRoot, packageJson, asyncRunLocks, gitIndexWrite) : packageJson.hasVerify ? { name: "pnpm verify", - command: ["pnpm", "verify"], + command: ["pnpm", "run", "verify"], executionProfile: "default", env: {}, reason: "repo exposes a verify script" diff --git a/package.json b/package.json index ff9519b..a62f4ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@async/auto-git", - "version": "0.2.2", + "version": "0.3.0", "description": "Auto Git skill suite for intent-based git commits, release checks, and repository handoffs.", "type": "module", "license": "MIT", @@ -14,45 +14,59 @@ }, "scripts": { "async-pipeline": "node scripts/run-async-pipeline.js", + "build": "node scripts/build-dist.js", "gists:check": "node scripts/package-gists.js --check", "gists:package": "node scripts/package-gists.js --write", "gists:publish": "node scripts/publish-gists.js", - "pipeline:github:check": "node scripts/run-async-pipeline.js github check", - "pipeline:github:generate": "node scripts/run-async-pipeline.js github generate", - "pipeline:verify": "node scripts/run-async-pipeline.js run verify", - "release:check": "pnpm verify && npm --cache ./.async/npm-cache pack --dry-run", - "release:doctor": "node scripts/release-doctor.mjs", - "release:pack": "npm --cache ./.async/npm-cache pack --dry-run", - "release:publish": "node scripts/publish-npm.mjs", + "pipeline:api-surface": "async-pipeline run-task api-surface", + "pipeline:api-surface:generate": "async-pipeline run-task api-surface-generate", + "pipeline:github:check": "async-pipeline github check", + "pipeline:github:generate": "async-pipeline github generate", + "pipeline:pack": "async-pipeline run-task pack", + "pipeline:pages": "async-pipeline run pages", + "pipeline:preview": "async-pipeline run preview", + "pipeline:publish": "async-pipeline run publish", + "pipeline:publish-gists": "async-pipeline run publish-gists", + "pipeline:publish-github": "async-pipeline run publish-github", + "pipeline:publish:github:main": "async-pipeline publish github main --package .", + "pipeline:publish:github:pr": "async-pipeline publish github pr --package .", + "pipeline:publish:github:release": "async-pipeline publish github release --package .", + "pipeline:publish:npm": "async-pipeline publish npm --package .", + "pipeline:release-doctor": "async-pipeline run release-doctor", + "pipeline:release:doctor": "async-pipeline release doctor --package .", + "pipeline:release:ensure": "async-pipeline release ensure --package .", + "pipeline:snapshot": "async-pipeline run snapshot", + "pipeline:sync:check": "async-pipeline sync check", + "pipeline:sync:generate": "async-pipeline sync generate", + "pipeline:verify": "async-pipeline run verify", + "release:check": "pnpm run pipeline:verify -- --force", "skills:validate": "node scripts/validate-skills.js", "test": "node --test tests/*.test.js", - "verify": "node scripts/validate-skills.js && node scripts/package-gists.js --check && node --test tests/*.test.js && node scripts/run-async-pipeline.js github check" + "verify": "pnpm run pipeline:verify" }, "bin": { - "auto-git": "bin/auto-git.js", - "auto-git-finish": "bin/auto-git-finish.js", - "auto-git-gate": "bin/auto-git-gate.js", - "auto-git-ledger": "bin/auto-git-ledger.js", - "auto-git-release-doctor": "bin/auto-git-release-doctor.js", - "auto-git-release-preflight": "bin/auto-git-release-preflight.js", - "auto-git-snapshot": "bin/auto-git-snapshot.js", - "auto-git-start": "bin/auto-git-start.js" + "auto-git": "dist/bin/auto-git.js", + "auto-git-finish": "dist/bin/auto-git-finish.js", + "auto-git-gate": "dist/bin/auto-git-gate.js", + "auto-git-ledger": "dist/bin/auto-git-ledger.js", + "auto-git-release-doctor": "dist/bin/auto-git-release-doctor.js", + "auto-git-release-preflight": "dist/bin/auto-git-release-preflight.js", + "auto-git-snapshot": "dist/bin/auto-git-snapshot.js", + "auto-git-start": "dist/bin/auto-git-start.js" }, "files": [ - "bin", + "API_SURFACE.md", + "api-contract.json", "CHANGELOG.md", + "dist", "LICENSE", - "README.md", - "docs/gists", - "gist-manifest.json", - "gists", - "scripts", - "skills" + "README.md" ], "publishConfig": { "access": "public" }, "devDependencies": { - "@async/pipeline": "0.2.4" + "@async/api-contract": "0.1.0", + "@async/pipeline": "0.4.3" } } diff --git a/pipeline.ts b/pipeline.ts index 7ce8a75..a462880 100644 --- a/pipeline.ts +++ b/pipeline.ts @@ -1,5 +1,23 @@ import { definePipeline, env, job, sh, task, trigger } from "@async/pipeline"; +const packageInputs = [ + "package.json", + "pnpm-lock.yaml", + ".npmrc", + "bin/**/*.js", + "scripts/**/*.js", + "scripts/**/*.mjs", + "skills/**", + "docs/gists/**", + "gists/**", + "gist-manifest.json", + "README.md", + "CHANGELOG.md", + "LICENSE", + "api-contract.json", + "API_SURFACE.md" +]; + export default definePipeline({ name: "auto-git", cache: "file:local", @@ -10,18 +28,35 @@ export default definePipeline({ manual: trigger.manual() }, sync: { - github: { - nodeVersion: 24, - cache: true - }, + github: true, tasks: { prefix: "pipeline", runners: ["package"], targets: [{ package: "@async/auto-git" }], - jobs: ["verify", "publish-gists", "publish", "release-doctor"], + jobs: [ + "pages", + "preview", + "publish", + "publish-gists", + "publish-github", + "release-doctor", + "snapshot", + "verify" + ], scripts: { + "api-surface": "run-task api-surface", + "api-surface:generate": "run-task api-surface-generate", "github:check": "github check", - "github:generate": "github generate" + "github:generate": "github generate", + "pack": "run-task pack", + "publish:github:main": "publish github main --package .", + "publish:github:pr": "publish github pr --package .", + "publish:github:release": "publish github release --package .", + "publish:npm": "publish npm --package .", + "release:doctor": "release doctor --package .", + "release:ensure": "release ensure --package .", + "sync:check": "sync check", + "sync:generate": "sync generate" } } }, @@ -33,15 +68,24 @@ export default definePipeline({ ], tooling: [ "scripts/**/*.js", + "scripts/**/*.mjs", "tests/**/*.test.js", "package.json", "pipeline.ts" ], generated: [ "gists/**", + "dist/**", ".github/workflows/async-pipeline.yml", - ".github/async-pipeline.lock.json" - ] + ".github/async-pipeline.lock.json", + ".async-pipeline/tasks.lock.json", + "API_SURFACE.md" + ], + docs: [ + "README.md", + "docs/**/*.md" + ], + package: packageInputs }, tasks: { "validate-skills": task({ @@ -61,49 +105,150 @@ export default definePipeline({ cache: true, run: sh`node --test tests/*.test.js` }), + docs: task({ + description: "Docs site source check for the GitHub Pages README site.", + inputs: ["docs"], + cache: true, + run: sh`node scripts/check-docs-site.js` + }), + "api-surface-generate": task({ + description: "Regenerate the @async/auto-git API surface review ledger from the checked-in manifest.", + inputs: ["api-contract.json"], + outputs: ["API_SURFACE.md"], + cache: false, + // TODO(@async/pipeline): replace this repo-local api-contract task with + // a pipeline-owned API surface sync command once @async/pipeline exposes one. + run: sh`pnpm api-contract ledger --manifest api-contract.json --out API_SURFACE.md` + }), + "api-surface": task({ + description: "API surface drift checks: validate the @async/auto-git manifest and generated review ledger.", + inputs: ["api-contract.json", "API_SURFACE.md"], + cache: true, + // TODO(@async/pipeline): replace this repo-local api-contract task with + // a pipeline-owned API surface check once @async/pipeline exposes one. + run: [ + sh`pnpm api-contract check --manifest api-contract.json`, + sh`pnpm api-contract ledger --manifest api-contract.json --check API_SURFACE.md` + ] + }), + "sync-check": task({ + description: "Generated workflow, lock, and package scripts still match pipeline.ts.", + inputs: [ + "pipeline.ts", + "package.json", + ".github/workflows/async-pipeline.yml", + ".github/async-pipeline.lock.json", + ".async-pipeline/tasks.lock.json" + ], + cache: false, + run: sh`pnpm async-pipeline sync check` + }), + build: task({ + description: "Build the generated package runtime surface expected by @async/pipeline's GitHub Packages lifecycle.", + inputs: ["package"], + outputs: ["dist/**"], + cache: false, + run: sh`node scripts/build-dist.js` + }), pack: task({ - dependsOn: ["test", "check-github"], - inputs: ["skills", "tooling", "generated"], + dependsOn: ["test", "docs", "api-surface", "sync-check", "build"], + inputs: ["package", "generated"], cache: false, run: sh`npm --cache ./.async/npm-cache pack --dry-run` }), - "check-github": task({ - inputs: ["tooling", "generated"], + preview: task({ + description: "Same-repo PRs publish an immutable 0.0.0-pr..sha. preview to GitHub Packages and update one install comment. Fork PRs skip.", + dependsOn: ["pack"], + inputs: ["package", "generated"], cache: false, - run: sh`pnpm async-pipeline github check` + run: sh`pnpm async-pipeline publish github pr --package .` + }), + snapshot: task({ + description: "Pushes to main publish an immutable 0.0.0-main.sha. snapshot to GitHub Packages and move the main dist-tag while the commit is still the branch head.", + dependsOn: ["pack"], + inputs: ["package", "generated"], + cache: false, + run: sh`pnpm async-pipeline publish github main --package .` }), "publish-gists": task({ - dependsOn: ["test", "check-github"], + description: "Publish the generated Auto Git skill gist packages with the repo-owned gist publisher.", + dependsOn: ["pack"], inputs: ["skills", "tooling", "generated"], cache: false, run: sh`node scripts/publish-gists.js` }), "publish-github": task({ - description: "Mirror the stable npm package to GitHub Packages before npm publishing.", + description: "Publish the stable GitHub Packages mirror through @async/pipeline.", + dependsOn: ["release-ensure"], + inputs: ["package", "generated"], + cache: false, + run: sh`pnpm async-pipeline publish github release --package .` + }), + "release-ensure": task({ + description: "Create or verify the release tag and GitHub Release before package publishing.", dependsOn: ["pack"], - inputs: ["skills", "tooling", "generated"], + inputs: ["package", "generated"], cache: false, - run: sh`node scripts/publish-github.mjs` + run: sh`pnpm async-pipeline release ensure --package .` }), - "publish-npm": task({ - description: "Publish the stable package to npm with provenance, skipping if the version already exists.", + publish: task({ + description: "Publish npm with provenance after the stable GitHub Packages mirror, then verify release state.", dependsOn: ["publish-github"], - inputs: ["skills", "tooling", "generated"], + inputs: ["package", "generated"], cache: false, - run: sh`node scripts/publish-npm.mjs` + run: [ + sh`pnpm async-pipeline publish npm --package .`, + sh`pnpm async-pipeline release doctor --package .` + ] }), "release-doctor": task({ - description: "Diagnose and repair tag, npm, GitHub Packages, GitHub Release, gist, and workflow consistency.", + description: "Verify tag, npm, GitHub Packages, and GitHub Release state through @async/pipeline.", dependsOn: ["pack"], - inputs: ["skills", "tooling", "generated"], + inputs: ["package", "generated"], cache: false, - run: sh`node scripts/release-doctor.mjs --repair` + run: sh`pnpm async-pipeline release doctor --package .` }) }, jobs: { verify: job({ - target: ["test", "check-github"], - trigger: ["pr", "main", "manual", "release"] + target: "pack", + trigger: ["pr", "main", "release", "manual"] + }), + pages: job({ + target: "docs", + trigger: ["pr", "main", "manual"], + github: { + pages: { + build: { kind: "jekyll", source: "./docs", destination: "./_site" } + } + } + }), + preview: job({ + target: "preview", + trigger: ["pr"], + env: { + GITHUB_TOKEN: env.secret("GITHUB_TOKEN") + }, + github: { + permissions: { + issues: "write", + packages: "write", + pullRequests: "write" + } + } + }), + snapshot: job({ + target: "snapshot", + trigger: ["main"], + env: { + GITHUB_TOKEN: env.secret("GITHUB_TOKEN") + }, + github: { + permissions: { + contents: "write", + packages: "write" + } + } }), "publish-gists": job({ target: "publish-gists", @@ -112,9 +257,22 @@ export default definePipeline({ GIST_TOKEN: env.secret("GIST_TOKEN") } }), + "publish-github": job({ + target: "publish-github", + trigger: ["manual"], + env: { + GITHUB_TOKEN: env.secret("GITHUB_TOKEN") + }, + github: { + permissions: { + contents: "write", + packages: "write" + } + } + }), publish: job({ - target: "publish-npm", - trigger: ["release", "manual"], + target: "publish", + trigger: ["manual", "release"], environment: { name: "npm-publish", url: "https://www.npmjs.com/package/@async/auto-git" @@ -128,6 +286,7 @@ export default definePipeline({ }, github: { permissions: { + contents: "read", packages: "write" } } @@ -135,20 +294,12 @@ export default definePipeline({ "release-doctor": job({ target: "release-doctor", trigger: ["manual"], - environment: { - name: "npm-publish", - url: "https://www.npmjs.com/package/@async/auto-git" - }, - requires: { - provenance: true - }, env: { GITHUB_TOKEN: env.secret("GITHUB_TOKEN"), NODE_AUTH_TOKEN: env.secret("NPM_TOKEN") }, github: { permissions: { - contents: "write", packages: "write" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0ed53d..f12a94f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,17 +8,27 @@ importers: .: devDependencies: + '@async/api-contract': + specifier: 0.1.0 + version: 0.1.0 '@async/pipeline': - specifier: 0.2.4 - version: 0.2.4 + specifier: 0.4.3 + version: 0.4.3 packages: - '@async/pipeline@0.2.4': - resolution: {integrity: sha512-uR4v3VV4XAdIQm5xmedvVDNrbr5dnSdH9XOENB9pcZV91A7sRWtl/3wEJLR2/RARYQG4k2T7aTEMKCL1AusyAw==} + '@async/api-contract@0.1.0': + resolution: {integrity: sha512-kyeqE/Smmwwadwh7Zvs0LZoXuIh7k+6G5rqdyWIjY6vHLZQ1emp9EpL/20m6DtmjauTJxMNn/R7FQACUwfBeUw==} + engines: {node: '>=24'} + hasBin: true + + '@async/pipeline@0.4.3': + resolution: {integrity: sha512-aY/4Tv+GEcXupS2nVsjeCsgBqT1OmuXjpznkfDIDuwYdlBGwjmXsPzERJLNZ3PAeTi4hNhRi+gDA/2ADkAc6gA==} engines: {node: '>=24'} hasBin: true snapshots: - '@async/pipeline@0.2.4': {} + '@async/api-contract@0.1.0': {} + + '@async/pipeline@0.4.3': {} diff --git a/scripts/auto-git.mjs b/scripts/auto-git.mjs index a83757e..977af83 100755 --- a/scripts/auto-git.mjs +++ b/scripts/auto-git.mjs @@ -48,7 +48,7 @@ function usage() { "", "Examples:", " auto-git snapshot --cwd \"$PWD\" --write-state", - " auto-git gate --cwd \"$PWD\" --profile auto -- pnpm verify", + " auto-git gate --cwd \"$PWD\" --profile auto -- pnpm run verify", " auto-git release-preflight --cwd \"$PWD\" --require-verification" ].join("\n"); } diff --git a/scripts/build-dist.js b/scripts/build-dist.js new file mode 100644 index 0000000..12794d9 --- /dev/null +++ b/scripts/build-dist.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node +import { chmod, cp, mkdir, rm } from "node:fs/promises"; +import { join } from "node:path"; + +const root = new URL("..", import.meta.url); +const dist = new URL("../dist/", import.meta.url); + +const copyEntries = [ + "bin", + "scripts/auto-git.mjs", + "scripts/release-doctor.mjs", + "skills", + "docs/gists", + "gists", + "gist-manifest.json", + "api-contract.json", + "API_SURFACE.md" +]; + +await rm(dist, { recursive: true, force: true }); +await mkdir(dist, { recursive: true }); + +for (const entry of copyEntries) { + await cp(new URL(`../${entry}`, import.meta.url), new URL(`../dist/${entry}`, import.meta.url), { + recursive: true + }); +} + +for (const bin of [ + "auto-git.js", + "auto-git-finish.js", + "auto-git-gate.js", + "auto-git-ledger.js", + "auto-git-release-doctor.js", + "auto-git-release-preflight.js", + "auto-git-snapshot.js", + "auto-git-start.js" +]) { + await chmod(join(root.pathname, "dist", "bin", bin), 0o755); +} diff --git a/scripts/check-docs-site.js b/scripts/check-docs-site.js new file mode 100644 index 0000000..bbece8e --- /dev/null +++ b/scripts/check-docs-site.js @@ -0,0 +1,9 @@ +#!/usr/bin/env node +import { readFile } from "node:fs/promises"; + +const index = await readFile(new URL("../docs/index.md", import.meta.url), "utf8"); + +if (!/^# Auto Git/m.test(index)) { + console.error("docs/index.md must start the GitHub Pages site with an Auto Git heading."); + process.exit(1); +} diff --git a/scripts/publish-github.mjs b/scripts/publish-github.mjs deleted file mode 100755 index cd75a60..0000000 --- a/scripts/publish-github.mjs +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node -import { spawnSync } from "node:child_process"; -import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join, resolve } from "node:path"; -import { readFile } from "node:fs/promises"; - -const GITHUB_REGISTRY = "https://npm.pkg.github.com"; -const repoRoot = resolve(new URL("..", import.meta.url).pathname); -const manifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")); -const spec = `${manifest.name}@${manifest.version}`; - -function fail(message) { - console.error(message); - process.exit(1); -} - -function isMissingVersion(result) { - const text = `${result.stdout ?? ""}${result.stderr ?? ""}`; - return result.status !== 0 && /(^|[\s])(E404|404)([\s]|$)|not found/i.test(text); -} - -function hasPublishedVersion(result) { - return result.status === 0 && result.stdout.trim() === manifest.version; -} - -const scope = manifest.name.match(/^@([^/]+)\//)?.[1]?.toLowerCase(); -const owner = (process.env.GITHUB_REPOSITORY_OWNER ?? scope ?? "").toLowerCase(); -if (!scope || !owner || scope !== owner) { - fail(`GitHub Packages requires the npm scope to match the repository owner. Package scope=${scope ?? "none"}, owner=${owner || "unknown"}.`); -} - -const token = process.env.GITHUB_TOKEN ?? process.env.NODE_AUTH_TOKEN; -if (!token) { - fail("Set GITHUB_TOKEN or NODE_AUTH_TOKEN with packages:write to publish to GitHub Packages."); -} - -const stagingDir = await mkdtemp(join(tmpdir(), "auto-git-github-packages-")); -const npmConfig = join(stagingDir, ".npmrc"); -await writeFile( - npmConfig, - `@${scope}:registry=${GITHUB_REGISTRY}\n//npm.pkg.github.com/:_authToken=${token}\n`, - "utf8" -); -await chmod(npmConfig, 0o600); - -function npm(args, options = {}) { - return spawnSync("npm", args, { - cwd: repoRoot, - encoding: "utf8", - stdio: options.inherit ? "inherit" : ["ignore", "pipe", "pipe"], - env: { - ...process.env, - NPM_CONFIG_CACHE: process.env.NPM_CONFIG_CACHE ?? join(repoRoot, ".async", "npm-cache"), - NPM_CONFIG_USERCONFIG: npmConfig - } - }); -} - -function viewVersion() { - return npm(["view", spec, "version", "--registry", GITHUB_REGISTRY]); -} - -async function waitForPublishedVersion() { - for (let attempt = 1; attempt <= 8; attempt += 1) { - const view = viewVersion(); - if (hasPublishedVersion(view)) { - return true; - } - if (attempt < 8) { - await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 2000)); - } - } - return false; -} - -try { - const view = viewVersion(); - if (hasPublishedVersion(view)) { - console.log(`${spec} is already published to GitHub Packages; skipping publish.`); - } else { - if (!isMissingVersion(view)) { - console.error(`${view.stdout ?? ""}${view.stderr ?? ""}`.slice(0, 2000)); - fail(`Could not determine whether ${spec} exists on GitHub Packages; refusing to guess.`); - } - console.log(`Publishing ${spec} to GitHub Packages.`); - const publish = npm(["publish", "--tag", "latest", "--ignore-scripts", "--registry", GITHUB_REGISTRY], { - inherit: true - }); - if (publish.status !== 0) { - if (await waitForPublishedVersion()) { - console.log(`${spec} appeared on GitHub Packages after a publish race; treating as success.`); - } else { - process.exit(publish.status ?? 1); - } - } - } - - const tag = npm(["dist-tag", "add", spec, "latest", "--registry", GITHUB_REGISTRY], { inherit: true }); - process.exit(tag.status ?? 1); -} finally { - await rm(stagingDir, { recursive: true, force: true }); -} diff --git a/scripts/publish-npm.mjs b/scripts/publish-npm.mjs deleted file mode 100755 index eb0312c..0000000 --- a/scripts/publish-npm.mjs +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env node -import { spawnSync } from "node:child_process"; -import { readFile } from "node:fs/promises"; -import { join, resolve } from "node:path"; - -const REGISTRY = "https://registry.npmjs.org/"; -const repoRoot = resolve(new URL("..", import.meta.url).pathname); -const manifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")); -const spec = `${manifest.name}@${manifest.version}`; - -function fail(message) { - console.error(message); - process.exit(1); -} - -function npm(args, options = {}) { - return spawnSync("npm", args, { - cwd: repoRoot, - encoding: "utf8", - stdio: options.inherit ? "inherit" : ["ignore", "pipe", "pipe"], - env: { - ...process.env, - NPM_CONFIG_CACHE: process.env.NPM_CONFIG_CACHE ?? join(repoRoot, ".async", "npm-cache") - } - }); -} - -function output(result) { - return `${result.stdout ?? ""}${result.stderr ?? ""}`; -} - -function isMissingVersion(result) { - return result.status !== 0 && /(^|[\s])(E404|404)([\s]|$)|not found/i.test(output(result)); -} - -function viewVersion() { - return npm(["view", spec, "version", "--registry", REGISTRY]); -} - -function hasPublishedVersion(result) { - return result.status === 0 && result.stdout.trim() === manifest.version; -} - -async function waitForPublishedVersion() { - for (let attempt = 1; attempt <= 8; attempt += 1) { - const view = viewVersion(); - if (hasPublishedVersion(view)) { - return true; - } - if (attempt < 8) { - await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 2000)); - } - } - return false; -} - -if (manifest.private) { - fail(`${manifest.name} is marked private; refusing to publish.`); -} - -const view = viewVersion(); -if (hasPublishedVersion(view)) { - console.log(`${spec} is already published to npm; skipping.`); - process.exit(0); -} -if (!isMissingVersion(view)) { - console.error(output(view).slice(0, 2000)); - fail(`Could not determine whether ${spec} exists on npm; refusing to guess.`); -} - -console.log(`Publishing ${spec} to npm with provenance.`); -const publish = npm( - ["publish", "--access", "public", "--registry", REGISTRY, "--provenance"], - { inherit: true } -); -if (publish.status === 0) { - process.exit(0); -} - -if (await waitForPublishedVersion()) { - console.log(`${spec} appeared on npm after a publish race; treating as success.`); - process.exit(0); -} - -process.exit(publish.status ?? 1); diff --git a/scripts/release-doctor.mjs b/scripts/release-doctor.mjs index fbb9e03..3a6ce22 100755 --- a/scripts/release-doctor.mjs +++ b/scripts/release-doctor.mjs @@ -200,8 +200,8 @@ function evaluate(facts) { function applyAction(action) { if (action.id === "push-tag") return run("git", ["push", "origin", tag], { inherit: true }).status === 0; if (action.id === "fetch-tag") return run("git", ["fetch", "origin", "tag", tag], { inherit: true }).status === 0; - if (action.id === "publish-npm") return run(process.execPath, ["scripts/publish-npm.mjs"], { inherit: true }).status === 0; - if (action.id === "publish-github") return run(process.execPath, ["scripts/publish-github.mjs"], { inherit: true }).status === 0; + if (action.id === "publish-npm") return run("pnpm", ["async-pipeline", "publish", "npm", "--package", "."], { inherit: true }).status === 0; + if (action.id === "publish-github") return run("pnpm", ["async-pipeline", "publish", "github", "release", "--package", "."], { inherit: true }).status === 0; if (action.id === "create-release") return run("gh", ["release", "create", tag, "--verify-tag", "--generate-notes", "--title", tag], { inherit: true }).status === 0; return false; } diff --git a/skills/auto-git/references/git-topology-lifecycles.md b/skills/auto-git/references/git-topology-lifecycles.md index c095715..79f7594 100644 --- a/skills/auto-git/references/git-topology-lifecycles.md +++ b/skills/auto-git/references/git-topology-lifecycles.md @@ -79,7 +79,7 @@ detect stale chats only when those chats used Auto Git and wrote ledger state. - If the snapshot recommends `executionProfile: loopback-capable`, start with that profile for the expensive gate instead of first running a doomed restricted command. - Prefer `auto-git gate --cwd "$PWD" --profile auto --quiet-seconds 60 -- [args...]` for long or environment-sensitive verification. It records the PID/process group, duration, failure class, and quiet process-tree diagnostics. - If npm/pnpm verification fails on HOME cache/log/config writes in a sandbox, retry the same repo-native command with `NO_UPDATE_NOTIFIER=1`, `NPM_CONFIG_CACHE=/private/tmp/-npm-cache`, and `NPM_CONFIG_LOGS_DIR=/private/tmp/-npm-logs`. -- If the repo is `async-pipeline`, full `pnpm release:check` should use the loopback-capable profile plus tmp npm cache/log dirs because some tests bind `127.0.0.1` and the release check can spawn npm pack. +- If the repo is `async-pipeline`, full `pnpm run release:check` should use the loopback-capable profile plus tmp npm cache/log dirs because some tests bind `127.0.0.1` and the release check can spawn npm pack. ## Lifecycle Mode Selection diff --git a/skills/auto-git/scripts/auto-git-snapshot.mjs b/skills/auto-git/scripts/auto-git-snapshot.mjs index 7407a8c..cf7499b 100755 --- a/skills/auto-git/scripts/auto-git-snapshot.mjs +++ b/skills/auto-git/scripts/auto-git-snapshot.mjs @@ -594,7 +594,7 @@ function packageManagerHints(repoRoot, packageJson, asyncRunLocks) { hints.push({ id: "async-pipeline-release-check", appliesWhen: ["running full async-pipeline release gate from Codex"], - commandName: "pnpm release:check", + commandName: "pnpm run release:check", retryEnv, executionProfile: "loopback-capable", startWithPlainCommand: false, @@ -609,8 +609,8 @@ function buildExecutionPlan(repoRoot, packageJson, asyncRunLocks, gitIndexWrite) const asyncPipeline = isAsyncPipelineRepo(repoRoot, packageJson); const verification = asyncPipeline ? { - name: "pnpm release:check", - command: ["pnpm", "release:check"], + name: "pnpm run release:check", + command: ["pnpm", "run", "release:check"], executionProfile: "loopback-capable", env: retryEnv, reason: "async-pipeline release checks may bind loopback and spawn npm pack" @@ -618,7 +618,7 @@ function buildExecutionPlan(repoRoot, packageJson, asyncRunLocks, gitIndexWrite) : packageJson.hasVerify ? { name: "pnpm verify", - command: ["pnpm", "verify"], + command: ["pnpm", "run", "verify"], executionProfile: "default", env: {}, reason: "repo exposes a verify script" diff --git a/tests/skill-suite.test.js b/tests/skill-suite.test.js index 103521c..86ca68b 100644 --- a/tests/skill-suite.test.js +++ b/tests/skill-suite.test.js @@ -30,17 +30,24 @@ test("manifest packages every skill into flat gist files", async () => { }); test("package exposes publishable Auto Git CLI bins", async () => { + execFileSync(process.execPath, [path.join(rootDir, "scripts/build-dist.js")], { cwd: rootDir }); + const packageJson = JSON.parse(await readFile(path.join(rootDir, "package.json"), "utf8")); assert.equal(packageJson.private, undefined); assert.equal(packageJson.publishConfig.access, "public"); - assert.equal(packageJson.devDependencies["@async/pipeline"], "0.2.4"); - assert.equal(packageJson.scripts["release:publish"], "node scripts/publish-npm.mjs"); - assert.equal(packageJson.scripts["release:doctor"], "node scripts/release-doctor.mjs"); - assert.equal(packageJson.bin["auto-git"], "bin/auto-git.js"); - assert.ok(packageJson.files.includes("bin")); + assert.equal(packageJson.devDependencies["@async/pipeline"], "0.4.3"); + assert.equal(packageJson.devDependencies["@async/api-contract"], "0.1.0"); + assert.equal(packageJson.scripts["pipeline:publish:npm"], "async-pipeline publish npm --package ."); + assert.equal(packageJson.scripts["pipeline:publish:github:release"], "async-pipeline publish github release --package ."); + assert.equal(packageJson.scripts["pipeline:release:doctor"], "async-pipeline release doctor --package ."); + assert.equal(packageJson.scripts["pipeline:release:ensure"], "async-pipeline release ensure --package ."); + assert.equal(packageJson.bin["auto-git"], "dist/bin/auto-git.js"); + assert.ok(packageJson.files.includes("dist")); + assert.ok(packageJson.files.includes("API_SURFACE.md")); + assert.ok(packageJson.files.includes("api-contract.json")); for (const [name, relativePath] of Object.entries(packageJson.bin)) { - assert.match(relativePath, /^bin\/.+\.js$/, `${name} uses an npm-safe bin wrapper`); + assert.match(relativePath, /^dist\/bin\/.+\.js$/, `${name} uses a generated npm-safe bin wrapper`); const filePath = path.join(rootDir, relativePath); const fileStat = await stat(filePath); assert.notEqual(fileStat.mode & 0o111, 0, `${name} points at an executable file`); @@ -54,6 +61,45 @@ test("package exposes publishable Auto Git CLI bins", async () => { assert.match(help.stdout, /Usage: auto-git /); }); +test("pipeline sync owns lifecycle scripts, workflow dispatch, pages, and API surface checks", async () => { + const packageJson = JSON.parse(await readFile(path.join(rootDir, "package.json"), "utf8")); + const workflow = await readFile(path.join(rootDir, ".github/workflows/async-pipeline.yml"), "utf8"); + const readme = await readFile(path.join(rootDir, "README.md"), "utf8"); + const gistReadme = await readFile(path.join(rootDir, "docs/gists/auto-git.md"), "utf8"); + const taskLock = JSON.parse(await readFile(path.join(rootDir, ".async-pipeline/tasks.lock.json"), "utf8")); + const apiSurface = await readFile(path.join(rootDir, "API_SURFACE.md"), "utf8"); + const apiContract = JSON.parse(await readFile(path.join(rootDir, "api-contract.json"), "utf8")); + + assert.equal(packageJson.scripts["pipeline:sync:check"], "async-pipeline sync check"); + assert.equal(packageJson.scripts["pipeline:api-surface"], "async-pipeline run-task api-surface"); + assert.equal(packageJson.scripts["pipeline:pages"], "async-pipeline run pages"); + assert.equal(packageJson.scripts["pipeline:preview"], "async-pipeline run preview"); + assert.equal(packageJson.scripts["pipeline:snapshot"], "async-pipeline run snapshot"); + assert.equal(packageJson.scripts["pipeline:publish-gists"], "async-pipeline run publish-gists"); + + assert.match(workflow, /type: choice/); + assert.match(workflow, /- "publish-gists"/); + assert.match(workflow, /pages-deploy/); + assert.match(workflow, /pnpm async-pipeline run preview/); + assert.match(workflow, /pnpm async-pipeline run snapshot/); + assert.match(workflow, /contents: write/); + + assert.ok(taskLock.commands.some((command) => command.name === "pipeline:publish:github:pr")); + assert.ok(taskLock.commands.some((command) => command.name === "pipeline:release:ensure")); + assert.equal(apiContract.packageName, "@async/auto-git"); + assert.match(apiSurface, /Auto Git CLI/); + assert.match(apiSurface, /bin\.auto-git-release-preflight/); + assert.match(apiSurface, /job\.publish-github/); + assert.match(apiSurface, /package\.dist-runtime/); + + for (const docs of [readme, gistReadme]) { + assert.doesNotMatch(docs, /auto-git gate[\s\S]*pnpm run verify/); + assert.match(docs, /pnpm run pipeline:verify/); + } + assert.match(readme, /GIST_TOKEN=\.\.\. pnpm run pipeline:publish-gists/); + assert.doesNotMatch(readme, /pnpm run (verify|test|gists:check|gists:publish|skills:validate)/); +}); + test("auto-git dispatcher prefers an explicit local source checkout", async () => { const sourceRoot = await mkdtemp(path.join(tmpdir(), "auto-git-source-")); const repo = await mkdtemp(path.join(tmpdir(), "auto-git-dispatch-repo-")); @@ -260,7 +306,7 @@ test("auto-git snapshot promotes async-pipeline hints into an execution plan", a { name: "async-pipeline-workspace", packageManager: "pnpm@10.20.0", - scripts: { "release:check": "pnpm build" } + scripts: { "release:check": "pnpm run build" } }, null, 2 @@ -268,7 +314,7 @@ test("auto-git snapshot promotes async-pipeline hints into an execution plan", a ); const result = snapshot(repo); - assert.equal(result.snapshot.executionPlan.verification.name, "pnpm release:check"); + assert.equal(result.snapshot.executionPlan.verification.name, "pnpm run release:check"); assert.equal(result.snapshot.executionPlan.verification.executionProfile, "loopback-capable"); assert.equal(result.snapshot.executionPlan.verification.env.NO_UPDATE_NOTIFIER, "1"); assert.ok( @@ -454,7 +500,7 @@ test("auto-git finish requires pushed branch and return to main for everything r }); snapshot( repo, - ["--write-state", "--record-verification", "pnpm verify", "--exit-code", "0", "--run-id", "finish-run"], + ["--write-state", "--record-verification", "pnpm run verify", "--exit-code", "0", "--run-id", "finish-run"], { AUTO_GIT_STATE_HOME: stateHome } ); @@ -524,7 +570,7 @@ test("auto-git finish accepts pushed merge evidence without PR handoff", async ( snapshot(repo, ["--write-state", "--claim-run", "auto-git do everything", "--run-id", "merge-run"], { AUTO_GIT_STATE_HOME: stateHome }); - snapshot(repo, ["--write-state", "--record-verification", "pnpm verify", "--exit-code", "0", "--run-id", "merge-run"], { + snapshot(repo, ["--write-state", "--record-verification", "pnpm run verify", "--exit-code", "0", "--run-id", "merge-run"], { AUTO_GIT_STATE_HOME: stateHome }); @@ -617,7 +663,7 @@ test("auto-git release preflight reuses clean same-head verification after branc git(repo, ["switch", "-c", "codex/release-fixture"]); git(repo, ["push", "-u", "origin", "codex/release-fixture"]); - snapshot(repo, ["--write-state", "--record-verification", "pnpm verify", "--exit-code", "0"], { + snapshot(repo, ["--write-state", "--record-verification", "pnpm run verify", "--exit-code", "0"], { AUTO_GIT_STATE_HOME: stateHome }); @@ -689,7 +735,7 @@ test("auto-git snapshot derives PR readiness and records PR handoffs", async () [ "--write-state", "--record-verification", - "pnpm verify", + "pnpm run verify", "--exit-code", "0", "--run-id",