diff --git a/.github/workflows/check-skills.yml b/.github/workflows/check-skills.yml index 8863b64bbcd..21fda5c35cc 100644 --- a/.github/workflows/check-skills.yml +++ b/.github/workflows/check-skills.yml @@ -1,11 +1,13 @@ # check-skills.yml — Drop this into your library repo's .github/workflows/ # -# Checks intent skills after a release and opens or updates one review PR when -# existing skills, artifact coverage, or workspace package coverage need review. +# Validates intent skills on PRs. After a release or manual run, 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. +# Triggers: pull requests touching skills/artifacts, new release published, or +# manual workflow_dispatch. # -# intent-workflow-version: 2 +# intent-workflow-version: 3 # # Template variables (replaced by `intent setup`): # @tanstack/router — e.g. @tanstack/query or my-workspace workspace @@ -13,6 +15,12 @@ name: Check Skills on: + pull_request: + paths: + - 'skills/**' + - '**/skills/**' + - '_artifacts/**' + - '**/_artifacts/**' release: types: [published] workflow_dispatch: {} @@ -22,8 +30,28 @@ permissions: pull-requests: write jobs: - check: + validate: + name: Validate intent skills + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install intent + run: npm install -g @tanstack/intent + + - name: Validate skills + run: intent validate --github-summary + + review: name: Check intent skill coverage + if: github.event_name != 'pull_request' runs-on: ubuntu-latest steps: - name: Checkout @@ -42,181 +70,7 @@ jobs: - name: Check skills id: stale run: | - 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 - 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 - - { - echo "review_items<> "$GITHUB_OUTPUT" - - - 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 + intent stale --github-review --package-label "@tanstack/router" - name: Open or update review PR if: steps.stale.outputs.has_review == 'true' diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml deleted file mode 100644 index 8f39716aa98..00000000000 --- a/.github/workflows/validate-skills.yml +++ /dev/null @@ -1,52 +0,0 @@ -# validate-skills.yml — Drop this into your library repo's .github/workflows/ -# -# Validates skill files on PRs that touch the skills/ directory. -# Ensures frontmatter is correct, names match paths, and files stay under -# the 500-line limit. - -name: Validate Skills - -on: - pull_request: - paths: - - 'skills/**' - - '**/skills/**' - -jobs: - validate: - name: Validate skill files - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install intent CLI - run: npm install -g @tanstack/intent - - - name: Find and validate skills - run: | - # Find all directories containing SKILL.md files - SKILLS_DIR="" - if [ -d "skills" ]; then - SKILLS_DIR="skills" - elif [ -d "packages" ]; then - # Monorepo — find skills/ under packages - for dir in packages/*/skills; do - if [ -d "$dir" ]; then - echo "Validating $dir..." - intent validate "$dir" - fi - done - exit 0 - fi - - if [ -n "$SKILLS_DIR" ]; then - intent validate "$SKILLS_DIR" - else - echo "No skills/ directory found — skipping validation." - fi