Files
przeprogramowani 56484f5856 feat(skills): guide CLI setup, filtered downloads and safe updates (#41)
* feat(skills): guide CLI setup, named downloads and updates

* fix(skills): bound npm inventory checks on cold Windows runners

* fix(skills): align launch guide with 10xCards PRD journey

* fix(tests): make helper checks portable on Windows

* test(helpers): trace Windows npm pack startup

* fix(helpers): allow bounded Windows npm startup time

* fix(helpers): use released lesson-scoped skill filters

Correct setup/guide examples and sync ownership to match CLI 1.21.
Exercise the documented preview/write commands through CAC and the
real partial writer, preserving all three trees and the PRD schema.

Refs: https://github.com/przeprogramowani/10x-cli/pull/41

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-14 10:51:38 +02:00

495 lines
21 KiB
YAML

name: CI
run-name: ${{ github.event_name == 'workflow_dispatch' && format('release-release-{0}', inputs.operation) || github.event.head_commit.message || github.event.pull_request.title }}
on:
pull_request:
push:
branches: [main, master]
workflow_dispatch:
inputs:
cli_sha:
required: true
type: string
toolkit_sha:
required: true
type: string
evidence_run_id:
required: true
type: string
evidence_run_attempt:
required: true
type: string
evidence_artifact_id:
required: true
type: string
lease_generation:
required: true
type: string
operation:
required: true
type: string
permissions:
contents: read
actions: read
pull-requests: read
env:
RELEASE_CLI_SHA: ${{ inputs.cli_sha || github.event.pull_request.head.sha || github.sha }}
RELEASE_TOOLKIT_SHA: ${{ inputs.toolkit_sha }}
TOOLKIT_COORDINATED_RUN_ID: ${{ inputs.evidence_run_id }}
TOOLKIT_COORDINATED_RUN_ATTEMPT: ${{ inputs.evidence_run_attempt }}
TOOLKIT_COORDINATED_ARTIFACT_ID: ${{ inputs.evidence_artifact_id }}
RELEASE_LEASE_GENERATION: ${{ inputs.lease_generation }}
RELEASE_OPERATION: ${{ inputs.operation }}
jobs:
check:
runs-on: ubuntu-latest
steps:
# Pinned by SHA to prevent tag-swapping supply chain attacks.
# To update: verify the new SHA on the action's releases page,
# then update both the SHA and the version comment.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install --global npm@11.12.1 --ignore-scripts
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.8"
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Audit dependencies
run: bun pm ls --all 2>&1 | head -200
- name: Typecheck
run: bun run typecheck
- name: Lint
run: bun run lint
- name: Validate packaged CLI helpers and shared references
run: bun run validate:cli-skills
- name: Test (unit + integration, excludes smoke)
run: bun test tests/*.test.ts
- name: Build (node target)
run: bun run build
- name: Build (binary)
run: bun run build:binary
- name: Smoke tests (binary + package)
run: bun test tests/smoke/
check-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install --global npm@11.12.1 --ignore-scripts
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.8"
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Test (unit + integration, excludes smoke)
shell: pwsh
run: |
$files = Get-ChildItem -Path tests -Filter *.test.ts | ForEach-Object { $_.FullName }
bun test @files
- name: Build (node target)
run: bun run build
- name: Build (binary)
run: bun run build:binary
- name: Smoke tests (binary + package)
run: bun test tests/smoke/
coordinated:
# PRs validate the CLI locally on both OSes. Exact private evidence gates publication.
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'
name: Verified private CLI/API evidence
needs: [check, check-windows]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: "22"
# Paid content, private source and content-bearing failure logs stay in
# Toolkit. This public workflow downloads only a strict hash-only receipt.
- name: Require green private run for this exact candidate pair
run: node scripts/verify-coordinated-receipt.mjs
env:
GH_TOKEN: ${{ secrets.TOOLKIT_READ_TOKEN }}
TOOLKIT_COORDINATED_RUN_ID: ${{ inputs.evidence_run_id || vars.TOOLKIT_COORDINATED_RUN_ID }}
E2E_TOOLKIT_SHA: ${{ inputs.toolkit_sha || vars.TOOLKIT_CANDIDATE_SHA }}
E2E_CLI_SHA: ${{ inputs.cli_sha || github.event.pull_request.head.sha || github.sha }}
E2E_CANDIDATE_KIND: ${{ github.event_name == 'pull_request' && 'pr' || 'release' }}
E2E_LEASE_GENERATION: ${{ inputs.lease_generation }}
TOOLKIT_DISPATCH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && secrets.TOOLKIT_DISPATCH_TOKEN || '' }}
version-bootstrap:
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: '1.3.8'
- run: bun install --frozen-lockfile
- name: Retain bootstrap calculation (read-only, revalidated after human merge)
run: node scripts/prepare-version.mjs --bootstrap
env:
GH_TOKEN: ${{ github.token }}
VERSION_RECORD_DIR: ${{ runner.temp }}/version-records
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: version-preparation-${{ github.event.pull_request.number }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/version-records/${{ github.event.pull_request.number }}/version-preparation.json
if-no-files-found: warn
retention-days: 90
wake-coordinator:
needs: [check, check-windows]
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- name: Wake trusted Toolkit coordinator after ordinary master checks
env:
GH_TOKEN: ${{ secrets.TOOLKIT_DISPATCH_TOKEN }}
CLI_RUN_ID: ${{ github.run_id }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo 'Release coordination pending scoped credential/bootstrap prerequisites.' >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
gh workflow run release-coordinator.yml --repo przeprogramowani/10x-toolkit --ref master -f cli_run_id="$CLI_RUN_ID"
version:
needs: [check, check-windows, coordinated]
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
outputs:
new_version: ${{ steps.identity.outputs.version }}
should_release: ${{ steps.identity.outputs.should_release }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: '1.3.8'
- run: bun install --frozen-lockfile
- name: Verify committed automatic version, merged PR provenance and lease
id: identity
run: node scripts/release-preflight.mjs
env:
GH_TOKEN: ${{ github.token }}
TOOLKIT_DISPATCH_TOKEN: ${{ secrets.TOOLKIT_DISPATCH_TOKEN }}
RELEASE_PREFLIGHT_FILE: ${{ runner.temp }}/preflight.json
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: release-preflight-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/preflight.json
if-no-files-found: error
retention-days: 90
build-binaries:
name: Binary (${{ matrix.artifact }})
needs: [version]
if: needs.version.outputs.should_release == 'true'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: bun-linux-x64
artifact: 10x-linux-x64
- os: ubuntu-latest
target: bun-linux-arm64
artifact: 10x-linux-arm64
- os: macos-latest
target: bun-darwin-arm64
artifact: 10x-darwin-arm64
- os: macos-14
target: bun-darwin-x64
artifact: 10x-darwin-x64
- os: windows-latest
target: bun-windows-x64
artifact: 10x-windows-x64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: '1.3.8'
- run: bun install --frozen-lockfile
- name: Build binary from proven source
run: bun build --compile --minify src/index.ts --target ${{ matrix.target }} --outfile dist/${{ matrix.artifact }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: binary-${{ matrix.artifact }}-${{ github.run_id }}-${{ github.run_attempt }}
path: dist/${{ matrix.artifact }}
if-no-files-found: error
retention-days: 90
publish-npm:
needs: [version, build-binaries]
if: needs.version.outputs.should_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
steps:
- name: Set isolated release paths
run: |
printf '%s\n' \
"RELEASE_OUTPUT_DIR=${RUNNER_TEMP}/release-package" \
"RELEASE_ASSET_DIR=${RUNNER_TEMP}/release-binaries" \
"RELEASE_PREFLIGHT_FILE=${RUNNER_TEMP}/release-preflight/preflight.json" \
"NPM_CONFIG_USERCONFIG=${RUNNER_TEMP}/release-npmrc" >> "$GITHUB_ENV"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: '1.3.8'
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install --global npm@11.12.1 --ignore-scripts
- run: bun install --frozen-lockfile
- run: bun run build
- name: Pack frozen directory outside checkout
run: node scripts/release-identity.mjs pack
- name: Smoke the retained pre-publication pack in an isolated installation
run: |
node scripts/smoke-release-pack.mjs
- name: Select complete exact-attempt binary artifacts
id: binaries
run: node scripts/release-assets.mjs select
env:
GH_TOKEN: ${{ github.token }}
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
artifact-ids: ${{ steps.binaries.outputs.ids }}
merge-multiple: true
path: ${{ env.RELEASE_ASSET_DIR }}
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: release-preflight-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/release-preflight
- name: Record bounded public release manifest before mutation
run: node scripts/release-assets.mjs manifest
env:
GH_TOKEN: ${{ github.token }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: release-package-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ env.RELEASE_OUTPUT_DIR }}
if-no-files-found: error
retention-days: 90
- name: Publish exact directory once, then verify actual registry bytes
run: |
node -e 'require("fs").writeFileSync(process.env.NPM_CONFIG_USERCONFIG, "registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=" + process.env.NPM_TOKEN + "\n", {mode: 0o600})'
node scripts/release-identity.mjs publish
env:
GH_TOKEN: ${{ github.token }}
TOOLKIT_DISPATCH_TOKEN: ${{ secrets.TOOLKIT_DISPATCH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: published-result-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ env.RELEASE_OUTPUT_DIR }}/published-result.json
if-no-files-found: error
retention-days: 90
github-release:
needs: [version, build-binaries, publish-npm]
if: needs.version.outputs.should_release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
actions: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Select original binary artifact IDs
id: binaries
run: node scripts/release-assets.mjs select
env:
GH_TOKEN: ${{ github.token }}
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
artifact-ids: ${{ steps.binaries.outputs.ids }}
merge-multiple: true
path: ${{ runner.temp }}/release-binaries
- name: Revalidate lease immediately before GitHub release mutation
run: node scripts/release-identity.mjs lease
env:
GH_TOKEN: ${{ github.token }}
TOOLKIT_DISPATCH_TOKEN: ${{ secrets.TOOLKIT_DISPATCH_TOKEN }}
- name: Create release with exactly the original binary assets
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.version.outputs.new_version }}
run: |
gh release create "v${VERSION}" --verify-tag --generate-notes --target "$RELEASE_CLI_SHA" \
"$RUNNER_TEMP/release-binaries/10x-linux-x64" \
"$RUNNER_TEMP/release-binaries/10x-linux-arm64" \
"$RUNNER_TEMP/release-binaries/10x-darwin-arm64" \
"$RUNNER_TEMP/release-binaries/10x-darwin-x64" \
"$RUNNER_TEMP/release-binaries/10x-windows-x64.exe"
notify-slack:
needs: [check, check-windows, coordinated, version, publish-npm, github-release]
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- name: Build notification payload
id: payload
env:
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
MSG=$(printf '%s\n' "$COMMIT_MESSAGE" | head -1 | cut -c1-80)
echo "commit_msg=${MSG}" >> "$GITHUB_OUTPUT"
if [[ "${{ needs.check.result }}" != "success" || "${{ needs.check-windows.result }}" != "success" || ( "${{ needs.coordinated.result }}" != "success" && "${{ needs.coordinated.result }}" != "skipped" ) || "${{ needs.publish-npm.result }}" == "failure" || "${{ needs.version.result }}" == "failure" || "${{ needs.github-release.result }}" == "failure" ]]; then
echo "failed=true" >> "$GITHUB_OUTPUT"
else
echo "failed=false" >> "$GITHUB_OUTPUT"
fi
- name: Post to #observability
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "${{ steps.payload.outputs.failed == 'true' && '🔴' || needs.version.outputs.should_release == 'true' && needs.publish-npm.result == 'success' && '📦' || '🟢' }} 10x-cli pipeline",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "${{ steps.payload.outputs.failed == 'true' && '🔴' || needs.version.outputs.should_release == 'true' && needs.publish-npm.result == 'success' && '📦' || '🟢' }} 10x-cli${{ needs.version.outputs.should_release == 'true' && format(' — v{0}', needs.version.outputs.new_version) || '' }}"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ needs.check.result == 'success' && needs.check-windows.result == 'success' && '✅' || '❌' }} Tests (Linux/Windows) ${{ needs.coordinated.result == 'success' && '✅' || needs.coordinated.result == 'skipped' && '⏭️' || '❌' }} E2E ${{ needs.publish-npm.result == 'success' && '✅' || needs.publish-npm.result == 'skipped' && '⏭️' || '❌' }} npm ${{ needs.github-release.result == 'success' && '✅' || needs.github-release.result == 'skipped' && '⏭️' || '❌' }} Release"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "📝 <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|`${{ github.sha }}`> ${{ steps.payload.outputs.commit_msg }} · 👤 ${{ github.actor }} · <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|🔗 Workflow>${{ steps.payload.outputs.failed == 'true' && ' · 🔔 see #alerting' || '' }}"
}
]
}
]
}
- name: Post to #alerting
if: steps.payload.outputs.failed == 'true'
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_ALERTS_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "🚨 10x-cli pipeline failure",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "🚨 10x-cli — FAILURE"
}
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Tests (Linux/Windows)*\n${{ needs.check.result == 'success' && needs.check-windows.result == 'success' && '✅ passed' || '❌ failed' }}" },
{ "type": "mrkdwn", "text": "*E2E*\n${{ needs.coordinated.result == 'success' && '✅ passed' || needs.coordinated.result == 'skipped' && '⏭️ skipped' || '❌ failed' }}" },
{ "type": "mrkdwn", "text": "*npm*\n${{ needs.publish-npm.result == 'success' && '✅ published' || needs.publish-npm.result == 'skipped' && '⏭️ skipped' || '❌ failed' }}" },
{ "type": "mrkdwn", "text": "*Release*\n${{ needs.github-release.result == 'success' && '✅ created' || needs.github-release.result == 'skipped' && '⏭️ skipped' || '❌ failed' }}" }
]
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "📝 *Commit:* <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|`${{ github.sha }}`>\n${{ steps.payload.outputs.commit_msg }}\n\n👤 *Author:* ${{ github.actor }}"
},
"accessory": {
"type": "button",
"text": { "type": "plain_text", "text": "View Run" },
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "🔍 <${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|Commit diff> · 📜 <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Full logs> · 📦 <https://www.npmjs.com/package/@przeprogramowani/10x-cli|npm package>"
}
]
}
]
}