From b4452c50b9eede11d39c7798452cd018c811e25c Mon Sep 17 00:00:00 2001 From: Sarah Gerrard Date: Thu, 23 Apr 2026 16:39:41 -0700 Subject: [PATCH] update workflow --- .github/workflows/check-skills.yml | 323 ++++++++++++++++++----------- 1 file changed, 201 insertions(+), 122 deletions(-) diff --git a/.github/workflows/check-skills.yml b/.github/workflows/check-skills.yml index 9888015bb04..8863b64bbcd 100644 --- a/.github/workflows/check-skills.yml +++ b/.github/workflows/check-skills.yml @@ -1,15 +1,14 @@ # check-skills.yml — Drop this into your library repo's .github/workflows/ # -# Checks for stale intent skills after a release and opens a review PR -# if any skills need attention. The PR body includes a prompt you can -# paste into Claude Code, Cursor, or any coding agent to update them. +# Checks intent skills after a release and opens or updates one review PR when +# existing skills, artifact coverage, or workspace package coverage need review. # # Triggers: new release published, or manual workflow_dispatch. # -# Template variables (replaced by `intent setup`): -# @tanstack/react-router — e.g. @tanstack/query +# intent-workflow-version: 2 # -# Adapted for TanStack Router monorepo: loops over all packages with skills. +# Template variables (replaced by `intent setup`): +# @tanstack/router — e.g. @tanstack/query or my-workspace workspace name: Check Skills @@ -24,7 +23,7 @@ permissions: jobs: check: - name: Check for stale skills + name: Check intent skill coverage runs-on: ubuntu-latest steps: - name: Checkout @@ -37,136 +36,216 @@ jobs: with: node-version: 20 - - name: Install intent CLI + - name: Install intent run: npm install -g @tanstack/intent - - name: Install dependencies - run: npm install --ignore-scripts - env: - npm_config_legacy_peer_deps: 'true' - - - name: Check staleness + - name: Check skills id: stale run: | - # Monorepo: collect stale reports from all packages with skills - ALL_OUTPUT="[" - FIRST=true - for dir in packages/*/; do - if [ -d "$dir/skills" ]; then - PKG_OUTPUT=$(cd "$dir" && npx @tanstack/intent stale --json 2>/dev/null) || true - if [ -n "$PKG_OUTPUT" ] && [ "$PKG_OUTPUT" != "[]" ] && [ "$PKG_OUTPUT" != "No intent-enabled packages found." ]; then - if [ "$FIRST" = true ]; then - FIRST=false - else - ALL_OUTPUT="${ALL_OUTPUT}," - fi - # Strip outer brackets and append entries - ENTRIES=$(echo "$PKG_OUTPUT" | node -e " - const input = require('fs').readFileSync('/dev/stdin','utf8').trim(); - try { const arr = JSON.parse(input); process.stdout.write(JSON.stringify(arr).slice(1, -1)); } catch {} - ") - ALL_OUTPUT="${ALL_OUTPUT}${ENTRIES}" - fi - fi - done - ALL_OUTPUT="${ALL_OUTPUT}]" - - echo "$ALL_OUTPUT" - - # Check if any skills need review - NEEDS_REVIEW=$(echo "$ALL_OUTPUT" | node -e " - const input = require('fs').readFileSync('/dev/stdin','utf8'); - try { - const reports = JSON.parse(input); - const stale = reports.flatMap(r => - r.skills.filter(s => s.needsReview).map(s => ({ library: r.library, skill: s.name, reasons: s.reasons })) - ); - if (stale.length > 0) { - console.log(JSON.stringify(stale)); - } - } catch {} - ") - - if [ -z "$NEEDS_REVIEW" ]; then - echo "has_stale=false" >> "$GITHUB_OUTPUT" + set +e + intent stale --json > stale.json + STATUS=$? + set -e + + cat stale.json + + if [ "$STATUS" -ne 0 ]; then + echo "has_review=true" >> "$GITHUB_OUTPUT" + echo "check_failed=true" >> "$GITHUB_OUTPUT" + cat > review-items.json <<'JSON' + [ + { + "type": "stale-check-failed", + "library": "@tanstack/router", + "subject": "intent stale --json", + "reasons": ["The stale check command failed. Review the workflow logs before updating skills."] + } + ] + JSON else - echo "has_stale=true" >> "$GITHUB_OUTPUT" - # Escape for multiline GH output - EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) - echo "stale_json<<$EOF" >> "$GITHUB_OUTPUT" - echo "$NEEDS_REVIEW" >> "$GITHUB_OUTPUT" - echo "$EOF" >> "$GITHUB_OUTPUT" + node <<'NODE' + const fs = require('fs') + const reports = JSON.parse(fs.readFileSync('stale.json', 'utf8')) + const items = [] + + for (const report of reports) { + for (const skill of report.skills ?? []) { + if (!skill?.needsReview) continue + items.push({ + type: 'stale-skill', + library: report.library, + subject: skill.name, + reasons: skill.reasons ?? [], + }) + } + + for (const signal of report.signals ?? []) { + if (signal?.needsReview === false) continue + items.push({ + type: signal?.type ?? 'review-signal', + library: signal?.library ?? report.library, + subject: + signal?.packageName ?? + signal?.packageRoot ?? + signal?.skill ?? + signal?.artifactPath ?? + signal?.subject ?? + report.library, + reasons: signal?.reasons ?? [], + artifactPath: signal?.artifactPath, + packageName: signal?.packageName, + packageRoot: signal?.packageRoot, + skill: signal?.skill, + }) + } + } + + fs.writeFileSync('review-items.json', JSON.stringify(items, null, 2) + '\n') + fs.appendFileSync( + process.env.GITHUB_OUTPUT, + `has_review=${items.length > 0 ? 'true' : 'false'}\n`, + ) + NODE fi - - name: Build summary - if: steps.stale.outputs.has_stale == 'true' - id: summary - run: | - node -e " - const stale = JSON.parse(process.env.STALE_JSON); - const lines = stale.map(s => - '- **' + s.skill + '** (' + s.library + '): ' + s.reasons.join(', ') - ); - const summary = lines.join('\n'); - - const prompt = [ - 'Review and update the following stale intent skills for TanStack Router:', - '', - ...stale.map(s => '- ' + s.skill + ': ' + s.reasons.join(', ')), - '', - 'For each stale skill:', - '1. Read the current SKILL.md file', - '2. Check what changed in the library since the skill was last updated', - '3. Update the skill content to reflect current APIs and behavior', - '4. Run \`npx @tanstack/intent validate\` to verify the updated skill', - ].join('\n'); - - // Write outputs - const fs = require('fs'); - const env = fs.readFileSync(process.env.GITHUB_OUTPUT, 'utf8'); - const eof = require('crypto').randomBytes(15).toString('base64'); - fs.appendFileSync(process.env.GITHUB_OUTPUT, - 'summary<<' + eof + '\n' + summary + '\n' + eof + '\n' + - 'prompt<<' + eof + '\n' + prompt + '\n' + eof + '\n' - ); - " - env: - STALE_JSON: ${{ steps.stale.outputs.stale_json }} + { + echo "review_items<> "$GITHUB_OUTPUT" - - name: Open review PR - if: steps.stale.outputs.has_stale == 'true' + - name: Write clean summary + if: steps.stale.outputs.has_review == 'false' + run: | + { + echo "### Intent skill review" + echo "" + echo "No stale skills or coverage gaps found." + } >> "$GITHUB_STEP_SUMMARY" + + - name: Build review PR body + if: steps.stale.outputs.has_review == 'true' + run: | + node <<'NODE' + const fs = require('fs') + const items = JSON.parse(fs.readFileSync('review-items.json', 'utf8')) + const grouped = new Map() + + for (const item of items) { + grouped.set(item.type, (grouped.get(item.type) ?? 0) + 1) + } + + const signalRows = [...grouped.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([type, count]) => `| \`${type}\` | ${count} |`) + + const itemRows = items.map((item) => { + const subject = item.subject ? `\`${item.subject}\`` : '-' + const reasons = item.reasons?.length ? item.reasons.join('; ') : '-' + return `| \`${item.type}\` | ${subject} | \`${item.library}\` | ${reasons} |` + }) + + const prompt = [ + 'You are helping maintain Intent skills for this repository.', + '', + 'Goal:', + 'Resolve the Intent skill review signals below while preserving the existing scope, taxonomy, and maintainer-reviewed artifacts.', + '', + 'Review signals:', + JSON.stringify(items, null, 2), + '', + 'Required workflow:', + '1. Read the existing `_artifacts/*domain_map.yaml`, `_artifacts/*skill_tree.yaml`, and generated `skills/**/SKILL.md` files.', + '2. Read each flagged package package.json, public exports, README/docs if present, and source entry points.', + '3. Compare flagged packages against the existing domains, skills, tasks, packages, covers, sources, tensions, and cross-references in the artifacts.', + '4. For each signal, decide whether it means existing skill coverage, a missing generated skill, a new skill candidate, out-of-scope coverage, or deferred work.', + '', + 'Maintainer questions:', + 'Before editing skills or artifacts, ask the maintainer:', + '1. For each flagged package, is this package user-facing enough to need agent guidance?', + '2. If yes, should it extend an existing skill or become a new skill?', + '3. If it extends an existing skill, which current skill should own it?', + '4. If it is out of scope, what short reason should be recorded in artifact coverage ignores?', + '5. Are any of these packages experimental or unstable enough to exclude for now?', + '', + 'Decision rules:', + '- Do not auto-generate skills.', + '- Do not create broad new skill areas without maintainer confirmation.', + '- Prefer adding package coverage to an existing skill when the package is an implementation variant of an existing domain.', + '- Create a new skill only when the package introduces a distinct developer task or failure mode.', + '- Preserve current naming, path, and package layout conventions.', + '- Keep generated skills under the package-local `skills/` directory.', + '- Keep repo-root `_artifacts` as the reviewed plan.', + '', + 'If maintainer confirms updates:', + '1. Update the relevant `_artifacts/*domain_map.yaml` or `_artifacts/*skill_tree.yaml`.', + '2. Update or create `SKILL.md` files only for confirmed coverage changes.', + '3. Keep `sources` aligned between artifact skill entries and SKILL frontmatter.', + '4. Bump `library_version` only for skills whose covered source package version changed.', + '5. Run `npx @tanstack/intent@latest validate` on touched skill directories.', + '6. Summarize every package as one of: existing-skill coverage, new skill, ignored, or deferred.', + ].join('\n') + + const body = [ + '## Intent Skill Review Needed', + '', + 'Intent found skills, artifact coverage, or workspace package coverage that need maintainer review.', + '', + '### Summary', + '', + '| Signal | Count |', + '| --- | ---: |', + ...signalRows, + '', + '### Review Items', + '', + '| Signal | Subject | Library | Reason |', + '| --- | --- | --- | --- |', + ...itemRows, + '', + '### Agent Prompt', + '', + 'Paste this into your coding agent:', + '', + '```text', + prompt, + '```', + '', + 'This PR is a review reminder only. It does not update skills automatically.', + ].join('\n') + + fs.writeFileSync('pr-body.md', body + '\n') + fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY, body + '\n') + NODE + + - name: Open or update review PR + if: steps.stale.outputs.has_review == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ github.event.release.tag_name || 'manual' }}" BRANCH="skills/review-${VERSION}" + BASE_BRANCH="${{ github.event.repository.default_branch }}" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -b "$BRANCH" - git commit --allow-empty -m "chore: review stale skills for ${VERSION}" - git push origin "$BRANCH" - - gh pr create \ - --title "Review stale skills (${VERSION})" \ - --body "$(cat <<'PREOF' - ## Stale Skills Detected - The following skills may need updates after the latest release: - - ${{ steps.summary.outputs.summary }} - - --- - - ### Update Prompt - - Paste this into your coding agent (Claude Code, Cursor, etc.): - - ~~~ - ${{ steps.summary.outputs.prompt }} - ~~~ + git fetch origin "$BRANCH" || true + if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH"; then + git checkout -B "$BRANCH" "origin/$BRANCH" + else + git checkout -b "$BRANCH" + git commit --allow-empty -m "chore: review intent skills for ${VERSION}" + git push origin "$BRANCH" + fi - PREOF - )" \ - --head "$BRANCH" \ - --base main + PR_URL="$(gh pr list --head "$BRANCH" --json url --jq '.[0].url')" + if [ -n "$PR_URL" ]; then + gh pr edit "$PR_URL" --body-file pr-body.md + else + gh pr create \ + --title "Review intent skills (${VERSION})" \ + --body-file pr-body.md \ + --head "$BRANCH" \ + --base "$BASE_BRANCH" + fi