fix: protect installs and artifact routing #244
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| jobs: | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| node-version: [20, 22] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node ${{ matrix.node-version }} | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm build | |
| - run: pnpm lint | |
| - run: pnpm run lint:architecture | |
| - run: pnpm format:check | |
| - run: pnpm test:coverage | |
| - name: Upload coverage to Codecov | |
| if: github.event_name == 'push' | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| flags: unittests | |
| name: comet-node-${{ matrix.node-version }} | |
| fail_ci_if_error: false | |
| script-smoke: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node 20 | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - name: Run Comet script smoke tests | |
| run: pnpm run test:script-smoke | |
| init-e2e: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| node-version: [20, 22] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Setup Node ${{ matrix.node-version }} | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm build | |
| - name: Install OpenSpec CLI | |
| run: npm install -g @fission-ai/openspec@latest | |
| - name: Create temp project directory | |
| run: mkdir -p "$RUNNER_TEMP/comet-e2e-project" | |
| shell: bash | |
| - name: Run comet init (project scope) | |
| run: | | |
| node bin/comet.js init "$RUNNER_TEMP/comet-e2e-project" --yes --json \ | |
| | tee "$RUNNER_TEMP/comet-init-project.json" | |
| shell: bash | |
| - name: Verify Comet skills installed (project) | |
| run: | | |
| PROJ="$RUNNER_TEMP/comet-e2e-project" | |
| check_file() { | |
| if [ ! -f "$1" ]; then | |
| echo "Missing file: $1" >&2 | |
| exit 1 | |
| fi | |
| } | |
| for sd in .claude/skills .cursor/skills .codex/skills .opencode/skills \ | |
| .windsurf/skills .cline/skills .roo/skills .continue/skills \ | |
| .gemini/skills .amazonq/skills .qwen/skills .kilocode/skills \ | |
| .augment/skills .kiro/skills .kimi-code/skills .lingma/skills .junie/skills \ | |
| .codebuddy/skills .cospec/skills .crush/skills .factory/skills \ | |
| .iflow/skills .pi/skills .qoder/skills .agents/skills \ | |
| .bob/skills .forge/skills .trae/skills .github/skills .zcode/skills \ | |
| .mimocode/skills; do | |
| check_file "$PROJ/$sd/comet/SKILL.md" | |
| check_file "$PROJ/$sd/comet/scripts/comet-guard.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-state.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-handoff.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-yaml-validate.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-archive.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-hook-guard.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-env.mjs" | |
| check_file "$PROJ/$sd/comet/scripts/comet-runtime.mjs" | |
| done | |
| check_file "$PROJ/.pi/extensions/comet-commands.ts" | |
| PROJ="$PROJ" node -e "const s=require(process.env.PROJ+'/.pi/settings.json'); if(s.enableSkillCommands!==true) process.exit(1)" | |
| echo "All 33 platforms project Comet skills: OK" | |
| shell: bash | |
| - name: Verify external installer status (project) | |
| run: | | |
| node - "$RUNNER_TEMP/comet-init-project.json" <<'NODE' | |
| const fs = require('fs'); | |
| function extractJsonPayload(raw) { | |
| const start = raw.indexOf('{'); | |
| if (start < 0) { | |
| throw new Error('No JSON payload found in init output'); | |
| } | |
| return raw.slice(start); | |
| } | |
| const raw = fs.readFileSync(process.argv[2], 'utf8'); | |
| const data = JSON.parse(extractJsonPayload(raw)); | |
| const allowed = new Set(['installed', 'skipped', 'failed']); | |
| const components = ['openspec', 'superpowers']; | |
| const malformed = data.results.flatMap((r) => | |
| components | |
| .filter((component) => !allowed.has(r[component])) | |
| .map((component) => `${r.platform}: ${component}=${r[component]}`), | |
| ); | |
| if (malformed.length > 0) { | |
| console.error('External installers returned unexpected status values:'); | |
| for (const item of malformed) { | |
| console.error(`- ${item}`); | |
| } | |
| process.exit(1); | |
| } | |
| const failed = data.results.flatMap((r) => | |
| components | |
| .filter((component) => r[component] === 'failed') | |
| .map((component) => `${r.platform}: ${component}=${r[component]}`), | |
| ); | |
| if (failed.length > 0) { | |
| console.warn('External installers reported failed for:'); | |
| for (const item of failed) { | |
| console.warn(`- ${item}`); | |
| } | |
| } | |
| console.log(`External installer statuses validated for ${data.results.length} platforms`); | |
| NODE | |
| shell: bash | |
| - name: Verify OpenSpec and working directories (project) | |
| run: | | |
| test -d "$RUNNER_TEMP/comet-e2e-project/openspec" | |
| test -d "$RUNNER_TEMP/comet-e2e-project/docs/superpowers/specs" | |
| test -d "$RUNNER_TEMP/comet-e2e-project/docs/superpowers/plans" | |
| echo "OpenSpec + working dirs: OK" | |
| shell: bash | |
| - name: Create temp global home | |
| run: mkdir -p "$RUNNER_TEMP/comet-e2e-global" | |
| shell: bash | |
| - name: Run comet init (global scope) | |
| run: | | |
| export HOME="$RUNNER_TEMP/comet-e2e-global" | |
| export USERPROFILE="$RUNNER_TEMP/comet-e2e-global" | |
| node bin/comet.js init "$RUNNER_TEMP/comet-e2e-global" --yes --scope global --json \ | |
| | tee "$RUNNER_TEMP/comet-init-global.json" | |
| shell: bash | |
| - name: Verify Comet skills installed (global) | |
| run: | | |
| HOME_DIR="$RUNNER_TEMP/comet-e2e-global" | |
| check_file() { | |
| if [ ! -f "$1" ]; then | |
| echo "Missing file: $1" >&2 | |
| exit 1 | |
| fi | |
| } | |
| for sd in .claude/skills .cursor/skills .codex/skills .config/opencode/skills \ | |
| .windsurf/skills .cline/skills .roo/skills .continue/skills \ | |
| .gemini/skills .amazonq/skills .qwen/skills .kilocode/skills \ | |
| .augment/skills .kiro/skills .kimi-code/skills .lingma/skills .junie/skills \ | |
| .codebuddy/skills .cospec/skills .crush/skills .factory/skills \ | |
| .iflow/skills .pi/agent/skills .qoder/skills .gemini/antigravity/skills \ | |
| .bob/skills .forge/skills .trae/skills .github/skills .zcode/skills \ | |
| .config/mimocode/skills; do | |
| check_file "$HOME_DIR/$sd/comet/SKILL.md" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-guard.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-state.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-handoff.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-yaml-validate.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-archive.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-hook-guard.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-env.mjs" | |
| check_file "$HOME_DIR/$sd/comet/scripts/comet-runtime.mjs" | |
| done | |
| check_file "$HOME_DIR/.pi/agent/extensions/comet-commands.ts" | |
| HOME_DIR="$HOME_DIR" node -e "const s=require(process.env.HOME_DIR+'/.pi/agent/settings.json'); if(s.enableSkillCommands!==true) process.exit(1)" | |
| echo "All 33 platforms global Comet skills: OK" | |
| shell: bash | |
| - name: Verify external installer status (global) | |
| run: | | |
| node - "$RUNNER_TEMP/comet-init-global.json" <<'NODE' | |
| const fs = require('fs'); | |
| function extractJsonPayload(raw) { | |
| const start = raw.indexOf('{'); | |
| if (start < 0) { | |
| throw new Error('No JSON payload found in init output'); | |
| } | |
| return raw.slice(start); | |
| } | |
| const raw = fs.readFileSync(process.argv[2], 'utf8'); | |
| const data = JSON.parse(extractJsonPayload(raw)); | |
| const allowed = new Set(['installed', 'skipped', 'failed']); | |
| const components = ['openspec', 'superpowers']; | |
| const malformed = data.results.flatMap((r) => | |
| components | |
| .filter((component) => !allowed.has(r[component])) | |
| .map((component) => `${r.platform}: ${component}=${r[component]}`), | |
| ); | |
| if (malformed.length > 0) { | |
| console.error('External installers returned unexpected status values:'); | |
| for (const item of malformed) { | |
| console.error(`- ${item}`); | |
| } | |
| process.exit(1); | |
| } | |
| const failed = data.results.flatMap((r) => | |
| components | |
| .filter((component) => r[component] === 'failed') | |
| .map((component) => `${r.platform}: ${component}=${r[component]}`), | |
| ); | |
| if (failed.length > 0) { | |
| console.warn('External installers reported failed for:'); | |
| for (const item of failed) { | |
| console.warn(`- ${item}`); | |
| } | |
| } | |
| console.log(`External installer statuses validated for ${data.results.length} platforms`); | |
| NODE | |
| shell: bash | |
| - name: Verify no working directories (global) | |
| run: | | |
| test ! -d "$RUNNER_TEMP/comet-e2e-global/docs/superpowers/specs" | |
| echo "Global scope has no working dirs: OK" | |
| shell: bash |