diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dda6a67 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Default: auto-detect and normalize to LF in the repo +* text=auto + +# Shell scripts must use LF everywhere (including Windows checkout) +*.sh text eol=lf +.shellcheckrc text eol=lf + +# Batch files must use CRLF +*.bat text eol=crlf +*.cmd text eol=crlf diff --git a/.github/release.yml b/.github/release.yml index f505bc2..47617ab 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -4,6 +4,8 @@ changelog: labels: ["type/feature"] - title: "Bug Fixes" labels: ["type/fix"] + - title: "Upstream Sync" + labels: ["upstream/sync"] - title: "Documentation" labels: ["type/docs"] - title: "Infrastructure & Evals" diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml new file mode 100644 index 0000000..0f4fef1 --- /dev/null +++ b/.github/workflows/build-extension.yml @@ -0,0 +1,185 @@ +name: Build Extension + +on: + push: + branches: [main, coatsy/vscode-extension] + paths: + - 'agents/**' + - 'skills/**' + - 'scripts/**' + - 'templates/**' + - 'reference/**' + - 'extension/**' + - 'extension-pack/**' + - 'upstream-version.json' + - '.github/workflows/build-extension.yml' + pull_request: + branches: [main, coatsy/vscode-extension] + paths: + - 'agents/**' + - 'skills/**' + - 'scripts/**' + - 'templates/**' + - 'reference/**' + - 'extension/**' + - 'extension-pack/**' + - 'upstream-version.json' + - '.github/workflows/build-extension.yml' + workflow_dispatch: + inputs: + version: + description: 'Version override (e.g. 0.2.0). Leave empty to use package.template.json version.' + required: false + type: string + +jobs: + lint-scripts: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v4 + + - name: Install ShellCheck (macOS) + if: runner.os == 'macOS' + run: brew install shellcheck + + - name: Validate shell script syntax + run: | + errors=0 + for script in extension/*.sh extension-pack/*.sh; do + echo "Checking $script..." + if ! bash -n "$script"; then + errors=$((errors + 1)) + fi + done + if [ "$errors" -gt 0 ]; then + echo "ERROR: $errors script(s) have syntax errors" + exit 1 + fi + + - name: Run ShellCheck + run: | + errors=0 + for script in extension/*.sh extension-pack/*.sh; do + echo "Checking $script..." + if ! shellcheck --severity=warning "$script"; then + errors=$((errors + 1)) + fi + done + if [ "$errors" -gt 0 ]; then + echo "ERROR: ShellCheck found issues in $errors script(s)" + exit 1 + fi + + build: + runs-on: ubuntu-latest + needs: lint-scripts + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Override version + if: inputs.version != '' + run: | + cd extension/templates + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.template.json', 'utf8')); + pkg.version = '${{ inputs.version }}'; + fs.writeFileSync('package.template.json', JSON.stringify(pkg, null, 2) + '\n'); + console.log('Version set to: ' + pkg.version); + " + + - name: Build extension + run: bash extension/test-local.sh --package-only + env: + CODE_CMD: 'true' # Skip VS Code install in CI + + - name: Verify VSIX artifact + run: | + VSIX=$(ls -t extension/*.vsix 2>/dev/null | head -1) + if [ -z "$VSIX" ]; then + echo "ERROR: No .vsix file produced" + exit 1 + fi + echo "Built: $VSIX" + echo "Size: $(du -h "$VSIX" | cut -f1)" + + - name: Validate VSIX contents + run: | + STAGE_DIR="extension/.staging" + + # Verify package.json has agents and skills + AGENTS=$(node -e "const p=require('./$STAGE_DIR/package.json'); console.log((p.contributes?.chatAgents||[]).length)") + SKILLS=$(node -e "const p=require('./$STAGE_DIR/package.json'); console.log((p.contributes?.chatSkills||[]).length)") + echo "Agents: $AGENTS, Skills: $SKILLS" + + if [ "$AGENTS" -lt 1 ]; then + echo "ERROR: No agents discovered" + exit 1 + fi + if [ "$SKILLS" -lt 1 ]; then + echo "ERROR: No skills discovered" + exit 1 + fi + + # Verify no Claude-specific frontmatter fields remain + if grep -r 'allowed-tools\|CLAUDE_SKILL_DIR\|user-invocable' "$STAGE_DIR/skills/" 2>/dev/null; then + echo "ERROR: Claude-specific fields found in staged skills" + exit 1 + fi + + # Verify path resolution handled all CLAUDE_SKILL_DIR variants + # Check for repo-root-relative (../../) and skill-local (./) patterns + echo "Checking resolved paths..." + UNRESOLVED=$(grep -rn '\${CLAUDE_SKILL_DIR}' "$STAGE_DIR/" 2>/dev/null || true) + if [ -n "$UNRESOLVED" ]; then + echo "ERROR: Unresolved CLAUDE_SKILL_DIR references:" + echo "$UNRESOLVED" + exit 1 + fi + echo " All CLAUDE_SKILL_DIR paths resolved" + + # Verify essential directories exist + for dir in agents skills scripts templates reference; do + if [ ! -d "$STAGE_DIR/$dir" ]; then + echo "ERROR: Missing directory: $dir" + exit 1 + fi + done + + echo "VSIX validation passed: $AGENTS agents, $SKILLS skills" + + - name: Upload skills extension VSIX + uses: actions/upload-artifact@v4 + with: + name: copilot-studio-skills-vsix + path: extension/*.vsix + retention-days: 30 + + - name: Build extension pack + run: bash extension-pack/build.sh --package-only + env: + CODE_CMD: 'true' + + - name: Verify extension pack VSIX + run: | + VSIX=$(ls -t extension-pack/*.vsix 2>/dev/null | head -1) + if [ -z "$VSIX" ]; then + echo "ERROR: No extension pack .vsix file produced" + exit 1 + fi + echo "Built: $VSIX" + echo "Size: $(du -h "$VSIX" | cut -f1)" + + - name: Upload extension pack VSIX + uses: actions/upload-artifact@v4 + with: + name: copilot-studio-bundle-vsix + path: extension-pack/*.vsix + retention-days: 30 diff --git a/.github/workflows/check-upstream-release.yml b/.github/workflows/check-upstream-release.yml new file mode 100644 index 0000000..a305867 --- /dev/null +++ b/.github/workflows/check-upstream-release.yml @@ -0,0 +1,367 @@ +name: Check upstream release + +on: + schedule: + # Every day at 08:00 UTC + - cron: "0 8 * * *" + workflow_dispatch: + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: coatsy/vscode-extension + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Fetch latest upstream release + id: upstream + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RESPONSE=$(curl -sf \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/microsoft/skills-for-copilot-studio/releases/latest") + + UPSTREAM_TAG=$(echo "$RESPONSE" | jq -r '.tag_name') + UPSTREAM_NAME=$(echo "$RESPONSE" | jq -r '.name') + UPSTREAM_URL=$(echo "$RESPONSE" | jq -r '.html_url') + UPSTREAM_DATE=$(echo "$RESPONSE" | jq -r '.published_at') + UPSTREAM_BODY=$(echo "$RESPONSE" | jq -r '.body') + + echo "tag=$UPSTREAM_TAG" >> "$GITHUB_OUTPUT" + echo "name=$UPSTREAM_NAME" >> "$GITHUB_OUTPUT" + echo "url=$UPSTREAM_URL" >> "$GITHUB_OUTPUT" + echo "date=$UPSTREAM_DATE" >> "$GITHUB_OUTPUT" + + # Store body in a file to handle multiline content + echo "$UPSTREAM_BODY" > /tmp/upstream-body.md + + echo "Latest upstream release: $UPSTREAM_TAG ($UPSTREAM_DATE)" + + - name: Read tracked version + id: tracked + run: | + if [ ! -f "upstream-version.json" ]; then + echo "version=none" >> "$GITHUB_OUTPUT" + echo "No upstream-version.json found — treating as first run" + else + TRACKED=$(jq -r '.upstream_version' upstream-version.json) + echo "version=$TRACKED" >> "$GITHUB_OUTPUT" + echo "Currently tracked upstream version: $TRACKED" + fi + + - name: Compare versions + id: compare + run: | + UPSTREAM="${{ steps.upstream.outputs.tag }}" + TRACKED="${{ steps.tracked.outputs.version }}" + + if [ "$UPSTREAM" = "$TRACKED" ]; then + echo "new_release=false" >> "$GITHUB_OUTPUT" + echo "Already tracking latest upstream version ($UPSTREAM). Nothing to do." + else + echo "new_release=true" >> "$GITHUB_OUTPUT" + echo "New upstream release detected: $UPSTREAM (was: $TRACKED)" + fi + + - name: Check for existing proposal issue + if: steps.compare.outputs.new_release == 'true' + id: existing + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.upstream.outputs.tag }}" + + # Search all pages of open issues/PRs with the upstream/sync label + EXISTING=$(gh api --paginate \ + -H "Accept: application/vnd.github+json" \ + "/repos/${{ github.repository }}/issues?state=open&labels=upstream/sync&per_page=100" \ + | jq -r -s --arg tag "$TAG" 'add | map(select(.title | contains($tag))) | length') + + if [ "$EXISTING" -gt 0 ]; then + echo "duplicate=true" >> "$GITHUB_OUTPUT" + echo "Proposal issue for $TAG already exists — skipping." + else + echo "duplicate=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create proposal issue + if: steps.compare.outputs.new_release == 'true' && steps.existing.outputs.duplicate == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.upstream.outputs.tag }}" + NAME="${{ steps.upstream.outputs.name }}" + URL="${{ steps.upstream.outputs.url }}" + DATE="${{ steps.upstream.outputs.date }}" + TRACKED="${{ steps.tracked.outputs.version }}" + BODY=$(cat /tmp/upstream-body.md) + + ISSUE_BODY=$(cat <<'ISSUE_EOF' + ## Upstream Release Detected + + | | | + |---|---| + | **New upstream version** | `TAG_PLACEHOLDER` | + | **Previous tracked version** | `TRACKED_PLACEHOLDER` | + | **Published** | DATE_PLACEHOLDER | + | **Release URL** | URL_PLACEHOLDER | + + ## Upstream Release Notes + + BODY_PLACEHOLDER + + ## Recommended Actions + + 1. Review the upstream release notes for breaking changes or new features + 2. Run the sync-upstream workflow or manually merge upstream changes + 3. Update `upstream-version.json` to `TAG_PLACEHOLDER` + 4. Update extension version to `TAG_PLACEHOLDER` in `extension/templates/package.template.json` and `extension-pack/package.json` + 5. Test the build locally with `bash extension/test-local.sh --package-only` + 6. Create a release tagged `TAG_PLACEHOLDER` + + ## Impact Assessment + + - [ ] Breaking changes reviewed + - [ ] New skills or agents identified + - [ ] Extension compatibility verified + - [ ] Build tested successfully + + --- + *This issue was created automatically by the check-upstream-release workflow.* + ISSUE_EOF + ) + + # Replace placeholders + ISSUE_BODY="${ISSUE_BODY//TAG_PLACEHOLDER/$TAG}" + ISSUE_BODY="${ISSUE_BODY//TRACKED_PLACEHOLDER/$TRACKED}" + ISSUE_BODY="${ISSUE_BODY//DATE_PLACEHOLDER/$DATE}" + ISSUE_BODY="${ISSUE_BODY//URL_PLACEHOLDER/$URL}" + ISSUE_BODY="${ISSUE_BODY//BODY_PLACEHOLDER/$BODY}" + + # Create the issue via API (avoids shell quoting issues with gh cli) + jq -n \ + --arg title "upstream: new release $TAG available" \ + --arg body "$ISSUE_BODY" \ + '{"title": $title, "body": $body, "labels": ["upstream/sync"]}' \ + | curl -sf \ + -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues" \ + -d @- + + echo "Created proposal issue for upstream release $TAG" + + - name: Create version bump branch and PR + if: steps.compare.outputs.new_release == 'true' && steps.existing.outputs.duplicate == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.upstream.outputs.tag }}" + VERSION="${TAG#v}" + TODAY=$(date -u +%Y-%m-%d) + BRANCH="chore/bump-to-${VERSION}-${TODAY}-${GITHUB_RUN_ID}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$BRANCH" + + # Update upstream-version.json + jq --arg v "$TAG" --arg d "$TODAY" \ + '.upstream_version = $v | .last_checked = $d' \ + upstream-version.json > tmp.json && mv tmp.json upstream-version.json + + # Update extension version to match upstream + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('extension/templates/package.template.json', 'utf8')); + pkg.version = '$VERSION'; + fs.writeFileSync('extension/templates/package.template.json', JSON.stringify(pkg, null, 2) + '\n'); + " + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('extension-pack/package.json', 'utf8')); + pkg.version = '$VERSION'; + fs.writeFileSync('extension-pack/package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + + # Prepend changelog entry for the new version + CHANGELOG="extension/CHANGELOG.md" + if [ -f "$CHANGELOG" ]; then + RELEASE_BODY=$(cat /tmp/upstream-body.md 2>/dev/null || echo "") + ENTRY="## ${VERSION}\n\n### Upstream Sync\n\n- Synced with upstream release [${TAG}](https://github.com/microsoft/skills-for-copilot-studio/releases/tag/${TAG})\n" + + # Insert after the "# Changelog" header + sed -i "s/^# Changelog$/# Changelog\n\n${ENTRY}/" "$CHANGELOG" + echo "Added changelog entry for ${VERSION}" + fi + + git add upstream-version.json extension/templates/package.template.json extension-pack/package.json extension/CHANGELOG.md + git commit -m "chore: bump version to ${VERSION} (upstream ${TAG})" + git push origin "$BRANCH" + + # Create draft PR + PR_BODY="Automated version bump to match upstream release ${TAG}. + + **Upstream release**: [${TAG}](https://github.com/microsoft/skills-for-copilot-studio/releases/tag/${TAG}) + + ### Files updated + - \`upstream-version.json\` → \`${TAG}\` + - \`extension/templates/package.template.json\` → \`${VERSION}\` + - \`extension-pack/package.json\` → \`${VERSION}\` + - \`extension/CHANGELOG.md\` → added \`${VERSION}\` entry + + --- + *This PR was created automatically by the check-upstream-release workflow.*" + + curl -sf \ + -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/pulls" \ + -d "$(jq -n \ + --arg title "chore: bump version to ${VERSION} (upstream ${TAG})" \ + --arg body "$PR_BODY" \ + --arg head "$BRANCH" \ + '{"title": $title, "body": $body, "head": $head, "base": "coatsy/vscode-extension", "draft": true}')" + + echo "Created version bump PR for ${TAG}" + + - name: Trigger upstream sync and build + if: steps.compare.outputs.new_release == 'true' && steps.existing.outputs.duplicate == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.upstream.outputs.tag }}" + + echo "Triggering sync-upstream workflow with upstream_version=$TAG..." + jq -n --arg tag "$TAG" '{"ref": "coatsy/vscode-extension", "inputs": {"upstream_version": $tag}}' \ + | curl -sf \ + -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/actions/workflows/sync-upstream.yml/dispatches" \ + -d @- + echo "Sync-upstream triggered" + + echo "Triggering build-extension workflow..." + curl -sf \ + -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/actions/workflows/build-extension.yml/dispatches" \ + -d '{"ref": "coatsy/vscode-extension"}' + echo "Build triggered successfully" + + - name: Close stale failure issues + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Find open failure issues from previous runs and close them + ISSUES=$(gh api --paginate \ + "repos/${{ github.repository }}/issues?state=open&labels=type/infra&per_page=100" \ + --jq '.[] | select(.title | startswith("ci: check-upstream-release workflow failed")) | .number') + + for ISSUE_NUM in $ISSUES; do + echo "Closing stale failure issue #$ISSUE_NUM" + jq -n \ + --arg body "Automatically closed — the check-upstream-release workflow succeeded on $(date -u +%Y-%m-%d)." \ + '{"body": $body}' \ + | curl -sf \ + -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUM/comments" \ + -d @- + curl -sf \ + -X PATCH \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUM" \ + -d '{"state": "closed", "state_reason": "completed"}' + done + + - name: Summary + run: | + TAG="${{ steps.upstream.outputs.tag }}" + TRACKED="${{ steps.tracked.outputs.version }}" + NEW="${{ steps.compare.outputs.new_release }}" + + echo "### Upstream Release Check :mag:" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| | |" >> "$GITHUB_STEP_SUMMARY" + echo "|---|---|" >> "$GITHUB_STEP_SUMMARY" + echo "| **Latest upstream** | \`$TAG\` |" >> "$GITHUB_STEP_SUMMARY" + echo "| **Tracked version** | \`$TRACKED\` |" >> "$GITHUB_STEP_SUMMARY" + echo "| **New release** | $NEW |" >> "$GITHUB_STEP_SUMMARY" + + notify-failure: + runs-on: ubuntu-latest + needs: check + if: failure() + permissions: + issues: write + steps: + - name: Open failure issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TITLE="ci: check-upstream-release workflow failed ($(date -u +%Y-%m-%d))" + RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + # Check for an existing open failure issue to avoid duplicates + EXISTING=0 + PAGE=1 + + while true; do + RESPONSE=$(curl -sf \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues?state=open&labels=type/infra&per_page=100&page=$PAGE") + + PAGE_COUNT=$(printf '%s' "$RESPONSE" | jq 'length') + if [ "$PAGE_COUNT" -eq 0 ]; then + break + fi + + MATCHING_COUNT=$(printf '%s' "$RESPONSE" | jq -r '[.[] | select(.title | startswith("ci: check-upstream-release workflow failed"))] | length') + EXISTING=$((EXISTING + MATCHING_COUNT)) + + if [ "$PAGE_COUNT" -lt 100 ]; then + break + fi + + PAGE=$((PAGE + 1)) + done + if [ "$EXISTING" -gt 0 ]; then + echo "Failure issue already open — skipping." + exit 0 + fi + + jq -n \ + --arg title "$TITLE" \ + --arg body "The daily upstream release check workflow failed.\n\n**Run**: $RUN_URL\n\nPlease investigate the workflow logs and fix the issue.\n\n---\n*This issue was created automatically.*" \ + '{"title": $title, "body": $body, "labels": ["type/infra"]}' \ + | curl -sf \ + -X POST \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues" \ + -d @- + + echo "Opened failure notification issue" diff --git a/.github/workflows/new-release.yml b/.github/workflows/new-release.yml index faf5ed1..c8b5c06 100644 --- a/.github/workflows/new-release.yml +++ b/.github/workflows/new-release.yml @@ -64,17 +64,46 @@ jobs: fi echo "New version: $NEW_VERSION" - # Update both version files + # Update Claude plugin version files jq --arg v "$NEW_VERSION" '.version = $v' .claude-plugin/plugin.json > tmp.json && mv tmp.json .claude-plugin/plugin.json jq --arg v "$NEW_VERSION" '.plugins[0].version = $v' .claude-plugin/marketplace.json > tmp.json && mv tmp.json .claude-plugin/marketplace.json + # Update VS Code extension version files to match upstream + if [ -f "extension/templates/package.template.json" ]; then + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('extension/templates/package.template.json', 'utf8')); + pkg.version = '$NEW_VERSION'; + fs.writeFileSync('extension/templates/package.template.json', JSON.stringify(pkg, null, 2) + '\n'); + " + echo "Updated extension/templates/package.template.json to $NEW_VERSION" + fi + if [ -f "extension-pack/package.json" ]; then + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('extension-pack/package.json', 'utf8')); + pkg.version = '$NEW_VERSION'; + fs.writeFileSync('extension-pack/package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + echo "Updated extension-pack/package.json to $NEW_VERSION" + fi + + # Update upstream-version.json if present + if [ -f "upstream-version.json" ]; then + TODAY=$(date -u +%Y-%m-%d) + jq --arg v "v$NEW_VERSION" --arg d "$TODAY" \ + '.upstream_version = $v | .last_checked = $d' \ + upstream-version.json > tmp.json && mv tmp.json upstream-version.json + echo "Updated upstream-version.json to v$NEW_VERSION" + fi + # Configure git git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" # Create branch, commit, and push git checkout -b "$BRANCH" - git add .claude-plugin/plugin.json .claude-plugin/marketplace.json + git add -A git commit -m "chore: bump version to ${NEW_VERSION} for ${BRANCH}" git push origin "$BRANCH" diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml new file mode 100644 index 0000000..beedab5 --- /dev/null +++ b/.github/workflows/publish-extension.yml @@ -0,0 +1,168 @@ +name: Publish Extension + +on: + release: + types: [published] + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run \u2014 package without publishing' + required: false + type: boolean + default: false + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Resolve version from release tag + if: github.event_name == 'release' + run: | + TAG="${{ github.event.release.tag_name }}" + # Strip leading 'v' if present + VERSION="${TAG#v}" + echo "RELEASE_VERSION=$VERSION" >> "$GITHUB_ENV" + echo "Resolved version from tag: $VERSION" + + - name: Validate version matches manifests + if: github.event_name == 'release' + run: | + SKILLS_VERSION=$(node -e " + const pkg = require('./extension/templates/package.template.json'); + console.log(pkg.version); + ") + BUNDLE_VERSION=$(node -e " + const pkg = require('./extension-pack/package.json'); + console.log(pkg.version); + ") + echo "Skills extension version: $SKILLS_VERSION" + echo "Extension pack version: $BUNDLE_VERSION" + echo "Release version: $RELEASE_VERSION" + echo "SKILLS_VERSION=$SKILLS_VERSION" >> "$GITHUB_ENV" + if [ "$SKILLS_VERSION" != "$RELEASE_VERSION" ]; then + echo "ERROR: Skills extension version ($SKILLS_VERSION) does not match release tag ($RELEASE_VERSION)" + exit 1 + fi + if [ "$BUNDLE_VERSION" != "$RELEASE_VERSION" ]; then + echo "ERROR: Extension pack version ($BUNDLE_VERSION) does not match release tag ($RELEASE_VERSION)" + exit 1 + fi + + - name: Validate upstream version tracking + if: github.event_name == 'release' + run: | + if [ ! -f "upstream-version.json" ]; then + echo "ERROR: upstream-version.json not found. See VERSIONING.md for policy." + exit 1 + fi + UPSTREAM_VERSION=$(node -e " + const uv = require('./upstream-version.json'); + const v = uv.upstream_version || ''; + console.log(v.replace(/^v/, '')); + ") + if [ -z "$UPSTREAM_VERSION" ]; then + echo "ERROR: upstream-version.json does not contain a valid upstream_version" + exit 1 + fi + echo "UPSTREAM_VERSION=$UPSTREAM_VERSION" >> "$GITHUB_ENV" + echo "Upstream version tracked: $UPSTREAM_VERSION" + + # Enforce synchronized versioning: extension version must match upstream + if [ "$SKILLS_VERSION" != "$UPSTREAM_VERSION" ]; then + echo "ERROR: Extension version ($SKILLS_VERSION) does not match upstream version ($UPSTREAM_VERSION)" + echo "See VERSIONING.md — this fork's version must match the upstream release version." + exit 1 + fi + + - name: Build skills extension VSIX + run: bash extension/test-local.sh --package-only + env: + CODE_CMD: 'true' + + - name: Verify skills extension VSIX + run: | + VSIX=$(ls -t extension/*.vsix 2>/dev/null | head -1) + if [ -z "$VSIX" ]; then + echo "ERROR: No skills extension .vsix file produced" + exit 1 + fi + echo "SKILLS_VSIX=$VSIX" >> "$GITHUB_ENV" + echo "Built: $VSIX ($(du -h "$VSIX" | cut -f1))" + + - name: Build extension pack + run: bash extension-pack/build.sh --package-only + env: + CODE_CMD: 'true' + + - name: Verify extension pack VSIX + run: | + VSIX=$(ls -t extension-pack/*.vsix 2>/dev/null | head -1) + if [ -z "$VSIX" ]; then + echo "ERROR: No extension pack .vsix file produced" + exit 1 + fi + echo "BUNDLE_VSIX=$VSIX" >> "$GITHUB_ENV" + echo "Built: $VSIX ($(du -h "$VSIX" | cut -f1))" + + - name: Publish skills extension to Marketplace + if: >- + (github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && inputs.dry_run == false)) + run: npx --yes @vscode/vsce publish --no-dependencies --packagePath "$SKILLS_VSIX" + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Publish extension pack to Marketplace + if: >- + (github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && inputs.dry_run == false)) + run: npx --yes @vscode/vsce publish --no-dependencies --packagePath "$BUNDLE_VSIX" + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Dry-run notice + if: github.event_name == 'workflow_dispatch' && inputs.dry_run == true + run: echo "Dry run — both VSIX files built but not published." + + - name: Append upstream version to release body + if: github.event_name == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + RELEASE_TAG="${{ github.event.release.tag_name }}" + CURRENT_BODY=$(jq -r '.release.body // ""' "$GITHUB_EVENT_PATH") + UPSTREAM_REF="\n---\n**Upstream version**: [${UPSTREAM_VERSION}](https://github.com/microsoft/skills-for-copilot-studio/releases/tag/${UPSTREAM_VERSION})" + + # Only append if not already present + if echo "$CURRENT_BODY" | grep -q "Upstream version"; then + echo "Upstream version reference already present — skipping." + else + NEW_BODY="${CURRENT_BODY}${UPSTREAM_REF}" + jq -n --arg body "$NEW_BODY" '{"body": $body}' | curl -sf \ + -X PATCH \ + -H "Authorization: token $GH_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/releases/${{ github.event.release.id }}" \ + -d @- + echo "Appended upstream version ($UPSTREAM_VERSION) to release notes" + fi + + - name: Upload skills extension VSIX + uses: actions/upload-artifact@v4 + with: + name: copilot-studio-skills-vsix + path: extension/*.vsix + retention-days: 30 + + - name: Upload extension pack VSIX + uses: actions/upload-artifact@v4 + with: + name: copilot-studio-bundle-vsix + path: extension-pack/*.vsix + retention-days: 30 diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 0000000..eb8dca3 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,113 @@ +name: Sync upstream + +on: + schedule: + # Every Monday at 06:00 UTC + - cron: "0 6 * * 1" + workflow_call: + inputs: + upstream_version: + description: "Upstream version that triggered this sync (set by check-upstream-release)" + required: false + type: string + workflow_dispatch: + inputs: + upstream_version: + description: "Upstream version that triggered this sync (leave empty for scheduled runs)" + required: false + type: string + +permissions: + contents: write + pull-requests: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Checkout fork + uses: actions/checkout@v4 + with: + ref: coatsy/vscode-extension + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Add upstream remote + run: | + git remote add upstream https://github.com/microsoft/skills-for-copilot-studio.git + git fetch upstream main + + - name: Check for new commits + id: check + run: | + BEHIND=$(git rev-list --count HEAD..upstream/main) + echo "behind=$BEHIND" >> "$GITHUB_OUTPUT" + echo "Upstream is $BEHIND commit(s) ahead" + + - name: Merge upstream + if: steps.check.outputs.behind != '0' + id: merge + run: | + BRANCH="chore/sync-upstream-$(date -u +%Y%m%d)" + git checkout -b "$BRANCH" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + if git merge upstream/main --no-edit; then + echo "conflict=false" >> "$GITHUB_OUTPUT" + else + echo "conflict=true" >> "$GITHUB_OUTPUT" + git merge --abort + fi + + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + + - name: Push and create PR + if: steps.check.outputs.behind != '0' && steps.merge.outputs.conflict == 'false' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="${{ steps.merge.outputs.branch }}" + BEHIND="${{ steps.check.outputs.behind }}" + + git push origin "$BRANCH" + + # Build PR body with optional upstream version reference + UPSTREAM_VERSION="${{ inputs.upstream_version }}" + if [ -n "$UPSTREAM_VERSION" ]; then + VERSION_LINE="**Upstream release**: [$UPSTREAM_VERSION](https://github.com/microsoft/skills-for-copilot-studio/releases/tag/$UPSTREAM_VERSION)" + else + VERSION_LINE="" + fi + + PR_BODY="Automated sync of upstream microsoft/skills-for-copilot-studio main into coatsy/vscode-extension. + + **Commits behind**: $BEHIND" + + if [ -n "$VERSION_LINE" ]; then + PR_BODY="$PR_BODY + $VERSION_LINE" + fi + + PR_BODY="$PR_BODY + + This PR was created automatically by the sync-upstream workflow." + + gh pr create \ + --base coatsy/vscode-extension \ + --head "$BRANCH" \ + --title "chore: sync $BEHIND commit(s) from upstream main${UPSTREAM_VERSION:+ ($UPSTREAM_VERSION)}" \ + --body "$PR_BODY" \ + --label "upstream/sync" \ + --draft + + - name: Report conflict + if: steps.check.outputs.behind != '0' && steps.merge.outputs.conflict == 'true' + run: | + echo "::warning::Merge conflicts detected when syncing upstream. Manual resolution required." + echo "Run locally:" + echo " git fetch upstream main" + echo " git merge upstream/main" + echo " # resolve conflicts" + echo " git push" diff --git a/.github/workflows/validate-versions.yml b/.github/workflows/validate-versions.yml new file mode 100644 index 0000000..dade468 --- /dev/null +++ b/.github/workflows/validate-versions.yml @@ -0,0 +1,63 @@ +name: Validate version consistency + +on: + pull_request: + branches: [coatsy/vscode-extension] + paths: + - 'extension/templates/package.template.json' + - 'extension-pack/package.json' + - 'upstream-version.json' + +jobs: + check-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate version files are in sync + run: | + SKILLS_VERSION=$(node -e " + const pkg = require('./extension/templates/package.template.json'); + console.log(pkg.version); + ") + BUNDLE_VERSION=$(node -e " + const pkg = require('./extension-pack/package.json'); + console.log(pkg.version); + ") + + echo "Skills extension version: $SKILLS_VERSION" + echo "Extension pack version: $BUNDLE_VERSION" + + ERRORS=0 + + # Check extension versions match each other + if [ "$SKILLS_VERSION" != "$BUNDLE_VERSION" ]; then + echo "::error::Skills extension version ($SKILLS_VERSION) does not match extension pack version ($BUNDLE_VERSION)" + ERRORS=$((ERRORS + 1)) + fi + + # Check versions match upstream-version.json if present + if [ -f "upstream-version.json" ]; then + UPSTREAM_VERSION=$(node -e " + const uv = require('./upstream-version.json'); + const v = uv.upstream_version || ''; + console.log(v.replace(/^v/, '')); + ") + echo "Upstream version: $UPSTREAM_VERSION" + + if [ -n "$UPSTREAM_VERSION" ] && [ "$SKILLS_VERSION" != "$UPSTREAM_VERSION" ]; then + echo "::error::Extension version ($SKILLS_VERSION) does not match upstream version ($UPSTREAM_VERSION). See VERSIONING.md." + ERRORS=$((ERRORS + 1)) + fi + fi + + if [ "$ERRORS" -gt 0 ]; then + echo "" + echo "Version files are out of sync. All version files must match:" + echo " - extension/templates/package.template.json" + echo " - extension-pack/package.json" + echo " - upstream-version.json (upstream_version, without 'v' prefix)" + exit 1 + fi + + echo "All version files are in sync ($SKILLS_VERSION)" diff --git a/.gitignore b/.gitignore index 4ce85bf..8449708 100644 --- a/.gitignore +++ b/.gitignore @@ -15,11 +15,24 @@ scripts/package-lock.json # AI agent local settings (not part of the plugin) .claude/ +.copilot-tracking/ # VS Code settings (optional - remove if you want to share settings) -.vscode/ +.vscode/* +!.vscode/launch.json +!.vscode/tasks.json .github/copilot-instructions.md +# VS Code dev host (local build from source) +vscode/ +.vscode-dev-data/ +.vscode-dev-extensions/ + +# Extension build artifacts +extension/.staging/ +extension/*.vsix +extension-pack/*.vsix + # Agent tmp files (in any subfolder) tmpclaude-* @@ -28,7 +41,15 @@ tmpclaude-* .token_cache_*.json .token_cache_*.dpapi +# VS Code extension build artifacts (generated by packaging/test scripts) +extension/.staging/ +extension/package.json +extension/*.vsix + # Environment files (if used in the future) .env .env.local nul + +# stupid macOS things +.DS_Store \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.plans/vscode-extension.md b/.plans/vscode-extension.md new file mode 100644 index 0000000..8d16ff7 --- /dev/null +++ b/.plans/vscode-extension.md @@ -0,0 +1,154 @@ +# Plan: VS Code Extension for Copilot Studio Skills + +_Issue: #1 — Create a VS Code extension_ + +## TL;DR + +Create a VS Code extension in the same repo (`extension/` directory) that packages the 4 agents, 28+ skills, scripts, templates, and reference files as a declarative Copilot Chat extension — following the HVE Core pattern. The extension should coexist with the existing Claude Code plugin support. The approach is: rename/symlink agent files to `.agent.md`, create a packaging pipeline that discovers artifacts and generates `package.json` contributes entries, and set up CI/CD for Marketplace publishing. + +## Key findings from discovery + +### Current repo structure (Claude Code plugin) + +- **4 agents** in `agents/*.md` — YAML frontmatter with `name`, `description`, `skills` fields +- **28+ skills** in `skills/*/SKILL.md` — frontmatter with `user-invocable`, `description`, `allowed-tools`, `context`, `agent` +- **Scripts** in `scripts/` — esbuild bundles (Node 18 CommonJS), referenced by skills via `${CLAUDE_SKILL_DIR}/../../scripts/` +- **Templates** in `templates/` — YAML recipes for topics, actions, knowledge, variables +- **Reference** in `reference/` — JSON schemas + connector YAML definitions +- **Hooks** in `hooks/hooks.json` — Claude Code session hooks for auto-delegation +- **Claude plugin metadata** in `.claude-plugin/` — `plugin.json` + `marketplace.json` + +### HVE Core extension pattern (target) + +- **100% declarative** — no runtime code, no `main` entry, no `extension.js` +- **`contributes` fields** in `package.json`: `chatAgents` (path to `.agent.md`), `chatSkills` (path to `SKILL.md`), `chatInstructions`, `chatPromptFiles` +- **File conventions**: agents must be `.agent.md`, skills must be `SKILL.md` +- **Extension kind**: `["workspace", "ui"]` for local + remote support +- **Category**: `["Chat"]` +- **Packaging**: PowerShell scripts discover artifacts, generate `package.json` contributes, copy files, run `vsce package` +- **`.vscodeignore`**: controls what goes into the VSIX + +### Compatibility challenges + +1. **Agent file extension**: Current `.md` needs to become `.agent.md` for VS Code chatAgents +2. **Tool references**: `allowed-tools: Bash, Read, Edit` are Claude Code tools — VS Code Copilot Chat uses different tool model +3. **Script paths**: Skills use `${CLAUDE_SKILL_DIR}/../../scripts/` (Claude variable) — need equivalent for VS Code +4. **Hooks**: `hooks.json` is Claude-specific — VS Code uses `chatAgents` registration for auto-delegation +5. **Frontmatter fields**: `context: fork`, `agent: copilot-studio-author` may not apply in VS Code + +## Decisions + +- **Same repo**: Extension lives in `extension/` directory alongside existing Claude Code plugin +- **Both platforms**: Maintain Claude Code plugin + add VS Code extension +- **Publisher**: Need to create a VS Code Marketplace publisher account +- **Agent files**: Rename from `.md` to `.agent.md` (update Claude plugin references if needed, or maintain both) + +## Steps + +### Phase 1: Agent file compatibility (prerequisite) + +1. **Rename agent files** from `agents/*.md` to `agents/*.agent.md` — VS Code chatAgents contribution point requires `.agent.md` extension. Verify Claude Code still works with the new extension. *If Claude Code requires `.md`, create symlinks or copy during packaging instead.* +2. **Audit frontmatter fields** across all agents and skills — identify which fields are Claude-specific vs VS Code-compatible. Document any needed adaptations. + +### Phase 2: Extension scaffold + +3. **Create `extension/` directory** with: + - `package.json` template (or `templates/package.template.json` like HVE Core) + - `.vscodeignore` — exclude dev files, node_modules, src/, tests/ + - `README.md` — Marketplace description + - `LICENSE` — copy of root LICENSE + - `icon.png` — extension icon (to be designed) + - `PACKAGING.md` — developer docs for the packaging process + +4. **Design `package.json` manifest** with: + - `name`, `displayName`: e.g. `copilot-studio-skills` / `Copilot Studio Skills` + - `publisher`: TBD (new Marketplace publisher) + - `extensionKind`: `["workspace", "ui"]` + - `engines.vscode`: `"^1.106.1"` (or current minimum) + - `categories`: `["Chat"]` + - `contributes.chatAgents`: 4 agents + - `contributes.chatSkills`: 28+ skills + - No `main`, no activation — purely declarative + +### Phase 3: Packaging pipeline + +5. **Create packaging script** (shell or PowerShell) that: + - Discovers all agent files in `agents/` + - Discovers all skill folders in `skills/` + - Generates `contributes.chatAgents` and `contributes.chatSkills` arrays + - Writes/updates `extension/package.json` + - *Depends on step 3* + +6. **Create build script** that: + - Copies agents, skills, scripts, templates, reference into `extension/` (or subdirectory) + - Copies agent `.md` → `.agent.md` if rename approach isn't taken + - Runs `vsce package --no-dependencies` to produce `.vsix` + - Cleans up temporary copies afterward + - *Depends on step 5* + +7. **Handle script path resolution** — skills reference scripts via `${CLAUDE_SKILL_DIR}/../../scripts/`. Determine how VS Code Copilot resolves script paths from skills bundled in an extension. Options: (a) adjust paths during packaging, (b) bundle scripts adjacent to skills, (c) use a VS Code-specific path variable if available. *Parallel with step 5* + +### Phase 4: Testing and validation + +8. **Local install test** — build VSIX, install via `code --install-extension`, verify agents and skills appear in Copilot Chat. *Depends on step 6* +9. **Remote environment test** — test in Codespaces or WSL to verify `extensionKind: ["workspace", "ui"]` works. *Depends on step 8* +10. **Claude Code regression test** — verify the Claude Code plugin still works with any file renames or restructuring. *Parallel with step 8* + +### Phase 5: CI/CD + +11. **Create GitHub Actions workflow** for extension packaging: + - Trigger on push to main (or release tags) + - Run prepare + package scripts + - Upload `.vsix` as artifact + - *Depends on step 6* + +12. **Create Marketplace publishing workflow**: + - Manual trigger or on release + - Publish `.vsix` to VS Code Marketplace via `vsce publish` + - Requires `VSCE_PAT` secret + - *Depends on step 11* + +### Phase 6: Publisher and Marketplace setup + +13. **Create VS Code Marketplace publisher** account at +14. **Configure publisher ID** in `package.json` +15. **Design extension icon** (`icon.png`) +16. **Write Marketplace README** for `extension/README.md` + +### Phase 7: Documentation + +17. **Write `extension/PACKAGING.md`** — developer guide for building and publishing +18. **Update root `README.md`** — add VS Code extension installation instructions alongside Claude Code plugin instructions +19. **Update `CONTRIBUTING.md`** — add extension development section + +## Relevant files + +- `agents/copilot-studio-author.md` — agent to register (rename to `.agent.md`) +- `agents/copilot-studio-manage.md` — agent to register +- `agents/copilot-studio-test.md` — agent to register +- `agents/copilot-studio-troubleshoot.md` — agent to register +- `skills/*/SKILL.md` — 28+ skills to register via `contributes.chatSkills` +- `scripts/*.bundle.js` — bundled scripts referenced by skills +- `scripts/package.json` — existing build configuration for scripts +- `templates/` — YAML templates used by authoring skills +- `reference/` — schemas and connector definitions used by skills +- `.claude-plugin/plugin.json` — existing Claude plugin metadata (must not break) +- `hooks/hooks.json` — Claude-specific hooks (not applicable to extension) + +## Verification + +1. Build VSIX locally: run packaging script, confirm `.vsix` file is produced +2. Install locally: `code --install-extension .vsix`, open Copilot Chat, verify all 4 agents appear with correct names/descriptions +3. Invoke skills: select an agent, confirm it can find and invoke its linked skills +4. Script execution: test a skill that runs a bundled script (e.g., `lookup-schema`) — verify script path resolves correctly +5. Claude Code validation: run `claude --plugin-dir .` and confirm existing plugin still works +6. CI check: push to branch, verify GitHub Actions workflow builds the VSIX successfully +7. Remote test: open in Codespaces, verify extension activates and agents/skills are available + +## Further considerations + +1. **Script path resolution in VS Code Copilot Chat**: The biggest unknown — how does VS Code Copilot resolve paths like `${CLAUDE_SKILL_DIR}/../../scripts/` from bundled skills? Recommendation: investigate HVE Core's `pr-reference` skill (which also uses scripts) to see the pattern, and experiment with a single skill first before packaging all 28. + +2. **Agent file naming**: Renaming `agents/*.md` → `agents/*.agent.md` is clean but may break Claude Code. Recommendation: test Claude Code with `.agent.md` first; if it fails, copy files during packaging instead. + +3. **Minimum VS Code version**: HVE Core requires `^1.106.1`. The `chatSkills` contribution point may have been introduced in a specific version. Recommendation: check VS Code release notes for the minimum version that supports all needed contribution points. diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..c22eb40 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,8 @@ +# ShellCheck configuration +# https://www.shellcheck.net/wiki/ +# Matches the CI lint-scripts job in .github/workflows/build-extension.yml +# +# Note: severity is not a supported .shellcheckrc directive. +# CI enforces --severity=warning on the command line. + +shell=bash diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f99ed66 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,44 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Extension (Dev Host)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${env:VSCODE_DEV_PATH}/scripts/code-cli.sh", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/extension/.staging", + "--user-data-dir=${workspaceFolder}/.vscode-dev-data", + "--extensions-dir=${workspaceFolder}/.vscode-dev-extensions" + ], + "outFiles": [ + "${workspaceFolder}/extension/.staging/**/*.js" + ], + "preLaunchTask": "Package Extension" + }, + { + "name": "Attach to Extension Host", + "type": "node", + "request": "attach", + "port": 5870, + "restart": true, + "outFiles": [ + "${workspaceFolder}/extension/.staging/**/*.js" + ] + }, + { + "name": "Launch Extension (Stable)", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/extension/.staging", + "--user-data-dir=${workspaceFolder}/.vscode-dev-data", + "--extensions-dir=${workspaceFolder}/.vscode-dev-extensions" + ], + "outFiles": [ + "${workspaceFolder}/extension/.staging/**/*.js" + ], + "preLaunchTask": "Package Extension" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b5d5404 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,29 @@ +{ + "version": "2.0.0", + "options": { + "shell": { + "executable": "C:\\Program Files\\Git\\bin\\bash.exe", + "args": ["-l", "-c"] + } + }, + "tasks": [ + { + "label": "Package Extension", + "type": "shell", + "command": "bash", + "args": ["extension/test-local.sh", "--package-only"], + "group": "build", + "problemMatcher": [], + "detail": "Stage artifacts and build the VSIX without installing" + }, + { + "label": "Build and Install Extension", + "type": "shell", + "command": "bash", + "args": ["extension/test-local.sh"], + "group": "build", + "problemMatcher": [], + "detail": "Stage artifacts, build the VSIX, and install into VS Code" + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1cbe1b..9c70edd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,7 @@ -# Contributing +--- +title: Contributing +description: "Local development setup, building scripts, and extension development for Skills for Copilot Studio" +--- ## Local development @@ -49,6 +52,82 @@ npm install npm run build ``` +## VS Code extension development + +The VS Code extension packages the same agents, skills, and scripts into a `.vsix` for GitHub Copilot Chat. See [extension/PACKAGING.md](extension/PACKAGING.md) for the complete packaging guide. + +### Adding a new agent + +1. Create a new `.md` file in `agents/` (e.g., `agents/copilot-studio-myagent.md`) +2. Include YAML frontmatter with `name`, `description`, and `skills` fields +3. Add agent instructions in the Markdown body + +The packaging script discovers all `.md` files in `agents/` automatically. Each file is renamed to `.agent.md` during staging (VS Code requires this suffix for `contributes.chatAgents`). + +### Adding a new skill + +1. Create a new directory under `skills/` (e.g., `skills/my-skill/`) +2. Add a `SKILL.md` file with YAML frontmatter containing `name` and `description` +3. Optionally add supporting Markdown files in the same directory + +The packaging script discovers all directories under `skills/` that contain a `SKILL.md` file and registers them as `contributes.chatSkills` entries. Claude Code-specific frontmatter fields (`allowed-tools`, `context`, `agent`, `argument-hint`, `user-invocable`) are stripped automatically during staging. + +### How artifact discovery works + +The packaging script (`extension/test-local.sh`) dynamically builds `package.json` at staging time: + +1. Scans `agents/` for `.md` files and registers each as a `chatAgents` entry +2. Scans `skills/` for subdirectories containing `SKILL.md` and registers each as a `chatSkills` entry +3. Writes the populated `contributes` section into the staged `package.json` + +No manual registration is needed. Add a file in the correct location and the build picks it up. + +### Testing extension changes locally + +```bash +# Build and install in one step +bash extension/test-local.sh + +# Or package only (no install) +bash extension/test-local.sh --package-only +``` + +After installing, reload VS Code and open Copilot Chat to verify your agents and skills appear. + +### Running the CI pipeline locally + +The CI workflow runs the same packaging script. To reproduce locally: + +```bash +CODE_CMD="true" bash extension/test-local.sh --package-only +``` + +Setting `CODE_CMD="true"` skips the VS Code install step, matching the CI environment. + +### Shell scripts + +Shell scripts under `extension/` are linted by [ShellCheck](https://www.shellcheck.net/) in CI. The repo includes a `.shellcheckrc` that sets `shell=bash` and `severity=warning` so local runs match CI. + +To lint locally (requires ShellCheck installed): + +```bash +shellcheck extension/*.sh +``` + +Or install via npm: + +```bash +npm install -g shellcheck +shellcheck extension/*.sh +``` + +Script conventions: + +- Use `#!/usr/bin/env bash` and `set -euo pipefail` +- Shell files must use LF line endings (enforced by `.gitattributes`) +- Quote all variable expansions: `"${var}"` not `$var` +- Use `[[ ... ]]` for conditionals, not `[ ... ]` + ## Plugin management ```bash @@ -73,13 +152,15 @@ npm run build ## Project structure -``` +```text .claude-plugin/ # Plugin manifest and marketplace config .github/plugin/ # GitHub Copilot Plugin manifest to speedup discovery +.github/workflows/ # CI/CD (build-extension.yml) agents/ # Sub-agent definitions (advisor, author, manage, test) evals/ # Scenario-based eval framework (harness, report, fixtures) scenarios/ # Eval definitions per scenario (.json) hooks/ # Eval-only hooks (skill tracing via PreToolUse) +extension/ # VS Code extension packaging (test-local.sh, templates, docs) hooks/ # Session hooks (agent routing) skills/ # Skill definitions (entry points + internal skills) int-patterns/ # Internal skill indexing the pattern library diff --git a/README.md b/README.md index edd6383..fee5ae0 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,50 @@ -# Skills for Copilot Studio +--- +title: Skills for Copilot Studio +description: "Author, test, and troubleshoot Microsoft Copilot Studio agents through YAML files using a VS Code extension or Claude Code plugin" +--- -A plugin for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [GitHub Copilot CLI](https://docs.github.com/en/copilot), and [VS Code](https://code.visualstudio.com/) that enables authoring, testing, and troubleshooting [Microsoft Copilot Studio](https://aka.ms/CopilotStudio) **STANDARD** agents through YAML files — directly from your terminal or editor. +A toolkit for authoring, testing, and troubleshooting [Microsoft Copilot Studio](https://aka.ms/CopilotStudio) agents through YAML files. Available as a VS Code extension for GitHub Copilot Chat and as a plugin for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). -Looking for the plugin for modern/enhanced agents? See the [New Microsoft Copilot Studio Plugin](https://github.com/microsoft/copilot-studio-plugin). +[![Upstream Version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fcoatsy%2Fskills-for-copilot-studio%2Fcoatsy%2Fvscode-extension%2Fupstream-version.json&query=%24.upstream_version&label=upstream&color=blue)](https://github.com/microsoft/skills-for-copilot-studio/releases) ## Prerequisites -- [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [GitHub Copilot CLI](https://docs.github.com/en/copilot), or [VS Code](https://code.visualstudio.com/) -- [Node.js](https://nodejs.org/) 18+ -- [VS Code](https://code.visualstudio.com/) with the [Copilot Studio Extension](https://github.com/microsoft/vscode-copilotstudio) (required for push/pull/clone operations) +* [Node.js](https://nodejs.org/) 22+ +* [VS Code](https://code.visualstudio.com/) with the [Copilot Studio Extension](https://github.com/microsoft/vscode-copilotstudio) (required for push/pull/clone operations) +* One of the following: + * [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extensions (for the VS Code extension) + * [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (for the CLI plugin) ## Installation -### From marketplace (Claude Code / GitHub Copilot CLI) +### VS Code extension (recommended) + +Install the **Copilot Studio Development Bundle** to get everything you need in one click: + +[Install Copilot Studio Development Bundle](https://marketplace.visualstudio.com/items?itemName=coatsy.copilot-studio-development-bundle) + +Or from the command line: ```bash -/plugin marketplace add microsoft/skills-for-copilot-studio -/plugin install copilot-studio@skills-for-copilot-studio +code --install-extension coatsy.copilot-studio-development-bundle ``` + +The bundle installs both the **Copilot Studio Skills** extension and the **Copilot Studio** extension together. Once installed, the agents and skills are available in GitHub Copilot Chat. See [SETUP_GUIDE.md](SETUP_GUIDE.md) for a full walkthrough. + ### From VS Code Extensions Store (GitHub Copilot) Search for **Skills for Copilot Studio** in the VS Code Extensions using the **@agentPlugins** filter to view and click **Install**. ![VS Code Extensions Store](./img/VSCodeStore.png) -### From a local clone +### Claude Code plugin from marketplace + +```bash +/plugin marketplace add microsoft/skills-for-copilot-studio +/plugin install copilot-studio@skills-for-copilot-studio +``` + +### Claude Code plugin from a local clone ```bash git clone https://github.com/microsoft/skills-for-copilot-studio.git @@ -57,7 +77,7 @@ The plugin provides four sub-agents, each backed by a specialized agent: /copilot-studio:copilot-studio-manage Clone, push, pull, and sync agent content between local files and the cloud /copilot-studio:copilot-studio-author Create and edit YAML (topics, actions, knowledge, triggers, variables) /copilot-studio:copilot-studio-test Test published agents — point-tests, batch suites, or evaluation analysis -/copilot-studio:copilot-studio-advisor Design guidance, agent review, and troubleshooting +/copilot-studio:copilot-studio-advisor Design guidance, agent review, and troubleshooting ``` ## Quick Start diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md index 16c12cb..a07d667 100644 --- a/SETUP_GUIDE.md +++ b/SETUP_GUIDE.md @@ -1,16 +1,26 @@ -# Skills for Copilot Studio — Setup Guide +--- +title: "Skills for Copilot Studio: Setup Guide" +description: "End-to-end walkthrough for installing the toolkit, cloning an agent, authoring changes, pushing, testing, and troubleshooting" +--- + +This guide walks you through setting up the toolkit and using it end-to-end: install, clone an agent, author changes, push, test, and troubleshoot. -This guide walks you through setting up the plugin and using it end-to-end: install, clone an agent, author changes, push, test, and troubleshoot. +The toolkit is available in two forms: + +* **VS Code extension** for GitHub Copilot Chat (recommended for VS Code users) +* **Claude Code plugin** for terminal-based workflows --- ## Prerequisites -| Requirement | Version | Verification | -|-------------|---------|-------------| -| Node.js | 18+ | `node --version` | -| Claude Code or GitHub Copilot CLI | Latest | `claude --version` or `copilot --version` | -| Copilot Studio VS Code Extension | Latest | [Install from marketplace](https://github.com/microsoft/vscode-copilotstudio) | +| Requirement | Version | Verification | +|-------------------------------------|---------|------------------------------------------------------------------------------------------------| +| Node.js | 22+ | `node --version` | +| VS Code | 1.106.1+| Required for the extension; also provides the LSP binary for push/pull/clone | +| Copilot Studio VS Code Extension | Latest | [Install from marketplace](https://github.com/microsoft/vscode-copilotstudio) | +| GitHub Copilot + Copilot Chat | Latest | Required for the VS Code extension path | +| Claude Code or GitHub Copilot CLI | Latest | Required for the Claude Code plugin path; `claude --version` or `copilot --version` | The VS Code extension provides the LanguageServerHost binary used for clone, push, and pull operations. VS Code itself does not need to be running. @@ -20,9 +30,23 @@ You also need access to a Power Platform environment with Copilot Studio and an --- -## 1. Install the Plugin +## 1. Install the Toolkit + +### Option A: VS Code extension bundle (recommended) -### Option A: Install from marketplace (recommended) +Install the **Copilot Studio Development Bundle** to get everything you need in one click: + +[Install Copilot Studio Development Bundle](https://marketplace.visualstudio.com/items?itemName=coatsy.copilot-studio-development-bundle) + +Or from the command line: + +```bash +code --install-extension coatsy.copilot-studio-development-bundle +``` + +The bundle installs both the **Copilot Studio Skills** extension and the **Copilot Studio** extension together. After installing, reload VS Code. The agents and skills appear in GitHub Copilot Chat. + +### Option B: Claude Code plugin from marketplace ```bash /plugin marketplace add microsoft/skills-for-copilot-studio @@ -31,7 +55,7 @@ You also need access to a Power Platform environment with Copilot Studio and an Once installed, the plugin is available globally. -### Option B: Run locally from a clone +### Option C: Claude Code plugin from a local clone ```bash git clone https://github.com/microsoft/skills-for-copilot-studio.git @@ -43,19 +67,24 @@ claude --plugin-dir /path/to/skills-for-copilot-studio claude plugin install /path/to/skills-for-copilot-studio --scope user ``` -To verify, type `@` in the input — you should see `copilot-studio:copilot-studio-manage`, `copilot-studio:copilot-studio-author`, `copilot-studio:copilot-studio-test`, and `copilot-studio:copilot-studio-advisor` in the autocomplete menu. +### Verify installation + +* **VS Code extension**: Open Copilot Chat and type `@`. You should see the Copilot Studio agents (Manage, Author, Test, Advisor) in the list. +* **Claude Code plugin**: Type `/` in the input. You should see `copilot-studio:copilot-studio-manage`, `copilot-studio:copilot-studio-author`, `copilot-studio:copilot-studio-test`, and `copilot-studio:copilot-studio-advisor` in the autocomplete menu. --- ## 2. Clone an Agent -### Option A: Clone via the plugin (recommended) +### Option A: Clone via the Manage agent -``` +In Copilot Chat (VS Code) or Claude Code, ask the Manage agent to clone an agent: + +```text @copilot-studio:copilot-studio-manage clone ``` -This walks you through environment selection, agent selection, and downloads the agent files — all with interactive browser auth (no app registration needed). +This walks you through environment selection, agent selection, and downloads the agent files with interactive browser auth (no app registration needed). ### Option B: Clone via VS Code @@ -67,25 +96,29 @@ After cloning, you should see `agent.mcs.yml`, `settings.mcs.yml`, and directori ## 3. Author Changes -Open Claude Code (or your preferred tool) in the cloned agent's directory. +Open VS Code (or Claude Code) in the cloned agent's directory. ### Explore the agent -``` +Ask the Author agent to describe the agent: + +```text @copilot-studio:copilot-studio-author What topics does this agent have? Give me an overview. ``` ### Create a new topic -``` +```text @copilot-studio:copilot-studio-author Create a new topic called "Product Information" that responds to questions about our products with a message listing our top 3 products. ``` -The agent generates a valid YAML file with unique IDs and saves it to the `topics/` directory. +The Author agent generates a valid YAML file with unique IDs and saves it to the `topics/` directory. ### Validate your changes -``` +Ask the Advisor agent to validate: + +```text @copilot-studio:copilot-studio-advisor Validate all topics in my agent ``` @@ -93,9 +126,11 @@ The agent generates a valid YAML file with unique IDs and saves it to the `topic ## 4. Push and Publish -### Push via the plugin (recommended) +### Push via the Manage agent (recommended) -``` +Ask the Manage agent to push: + +```text @copilot-studio:copilot-studio-manage push ``` @@ -113,42 +148,44 @@ Alternatively, use the Copilot Studio VS Code Extension: After pushing, **publish** in the Copilot Studio UI at [copilotstudio.microsoft.com](https://copilotstudio.microsoft.com): - Open your agent and click **Publish** -> **Important**: Pushing creates a **draft**. You must also **publish** to make changes live and testable via the plugin. +> [!IMPORTANT] +> Pushing creates a **draft**. You must also **publish** to make changes live and testable. --- ## 5. Test the Published Agent -The test agent (`@copilot-studio:copilot-studio-test`) supports three ways to test: +The Test agent (`@copilot-studio:copilot-studio-test`) supports three ways to test: ### Option A: Send a test message (point-test) Send a single utterance directly to the published agent and see its full response. Requires an **Azure App Registration**: -- **Platform**: Public client / Native (Mobile and desktop applications) -- **Redirect URI**: `http://localhost` (HTTP, not HTTPS) -- **API permissions**: Add a permission → APIs my organization uses → search **Power Platform API** → Delegated permissions → expand CopilotStudio → check `CopilotStudio.Copilots.Invoke` (optionally grant admin consent) -``` +* **Platform**: Public client / Native (Mobile and desktop applications) +* **Redirect URI**: `http://localhost` (HTTP, not HTTPS) +* **API permissions**: Add a permission, then APIs my organization uses, search **Power Platform API**, Delegated permissions, expand CopilotStudio, check `CopilotStudio.Copilots.Invoke` (optionally grant admin consent) + +```text @copilot-studio:copilot-studio-test Send "What products do you offer?" to the published agent ``` -The test agent will ask for your App Registration Client ID on first use, authenticate via device code flow, and return the agent's full response. Multi-turn is supported — the agent reuses the conversation automatically. +The Test agent asks for your App Registration Client ID on first use, authenticates via device code flow, and returns the agent's full response. Multi-turn is supported; the agent reuses the conversation automatically. ### Option B: Run a batch test suite (Copilot Studio Kit) If you have the [Power CAT Copilot Studio Kit](https://github.com/microsoft/Power-CAT-Copilot-Studio-Kit) installed in your environment, you can run pre-defined test sets with expected responses and pass/fail scoring. Requires an Azure App Registration with Dataverse permissions. -``` +```text @copilot-studio:copilot-studio-test Run my test suite ``` -The agent walks you through configuring the Dataverse connection on first use. +The Test agent walks you through configuring the Dataverse connection on first use. ### Option C: Analyze evaluation results Run evaluations in the Copilot Studio UI, export the results as CSV, and have the agent analyze failures and propose fixes: -``` +```text @copilot-studio:copilot-studio-test Analyze my evaluation results from ~/Downloads/Evaluate MyAgent.csv ``` @@ -158,13 +195,13 @@ Run evaluations in the Copilot Studio UI, export the results as CSV, and have th If the agent responds with incorrect or outdated information: -``` +```text @copilot-studio:copilot-studio-advisor The agent is making up product details that aren't accurate. It seems to be hallucinating instead of using real data. ``` -The advisor agent will diagnose the issue — in this case, the agent is generating ungrounded responses because it has no knowledge source to draw from. Fix it by adding one: +The Advisor agent diagnoses the issue. In this case, the agent is generating ungrounded responses because it has no knowledge source to draw from. Fix it by asking the Author agent: -``` +```text @copilot-studio:copilot-studio-author Add a knowledge source pointing to our product catalog at https://contoso.com/products ``` @@ -172,30 +209,39 @@ Then push, publish, and test again to verify the agent now responds with grounde --- +## Advanced Debugging with a Self-Hosted VS Code Build + +For extension development and deep integration debugging, you can build VS Code from source and use it as an isolated debug host. This avoids cross-contamination with your primary VS Code installation and lets you step through extension host internals. + +See [extension/docs/LOCAL_DEV_HOST.md](extension/docs/LOCAL_DEV_HOST.md) for the full setup guide and [extension/docs/DEBUG_CONFIG.md](extension/docs/DEBUG_CONFIG.md) for sample launch.json configurations. + +--- + ## Troubleshooting -| Issue | Possible Cause | Solution | -|-------|---------------|----------| -| Schema lookup returns "not found" | Definition name case mismatch | Use `search` to find the correct name | -| YAML parse error on import | Invalid YAML syntax | Check for indentation issues, missing colons | -| Topic doesn't render in canvas | Complex YAML not supported | Simplify the structure, use portal for complex edits | -| Duplicate ID error | Non-unique node IDs | Regenerate IDs for copied nodes | -| Power Fx error | Missing `=` prefix | Ensure expressions start with `=` | -| Plugin not found | Not installed or wrong path | Run `/plugin list` to verify | -| Extension not found (clone/push/pull) | Copilot Studio VS Code Extension not installed | [Install from marketplace](https://github.com/microsoft/vscode-copilotstudio) | -| ConcurrencyVersionMismatch on push | Stale row versions | Pull first, then push | +| Issue | Possible cause | Solution | +|----------------------------------------|---------------------------------------------------|---------------------------------------------------------------------------------| +| Schema lookup returns "not found" | Definition name case mismatch | Use `search` to find the correct name | +| YAML parse error on import | Invalid YAML syntax | Check for indentation issues, missing colons | +| Topic doesn't render in canvas | Complex YAML not supported | Simplify the structure, use portal for complex edits | +| Duplicate ID error | Non-unique node IDs | Regenerate IDs for copied nodes | +| Power Fx error | Missing `=` prefix | Ensure expressions start with `=` | +| Plugin not found | Not installed or wrong path | Run `/plugin list` to verify | +| Agents not visible in Copilot Chat | Extension not installed or not activated | Install the extension and reload VS Code | +| Extension not found (clone/push/pull) | Copilot Studio VS Code Extension not installed | [Install from marketplace](https://github.com/microsoft/vscode-copilotstudio) | +| ConcurrencyVersionMismatch on push | Stale row versions | Pull first, then push | -If something goes wrong, you can always re-clone the original agent with `@copilot-studio:copilot-studio-manage clone` or the VS Code Extension. +If something goes wrong, you can always re-clone the original agent with `@copilot-studio:copilot-studio-manage clone` or the Copilot Studio VS Code Extension. --- ## Summary Checklist -- [ ] Plugin installed from marketplace or loaded locally -- [ ] Copilot Studio VS Code Extension installed (provides the LSP binary) -- [ ] Agent cloned with `@copilot-studio:copilot-studio-manage clone` or VS Code Extension -- [ ] `@copilot-studio:copilot-studio-manage`, `:copilot-studio-author`, `:copilot-studio-test`, `:copilot-studio-advisor` visible in `@` autocomplete -- [ ] Created a topic with `@copilot-studio:copilot-studio-author` -- [ ] Validated with `@copilot-studio:copilot-studio-advisor` -- [ ] Pulled, pushed, and published (`@copilot-studio:copilot-studio-manage pull`, then `push`) -- [ ] Tested published agent with `@copilot-studio:copilot-studio-test` +* [ ] Toolkit installed (VS Code extension or Claude Code plugin) +* [ ] Copilot Studio VS Code Extension installed (provides the LSP binary for push/pull/clone) +* [ ] Agent cloned with `@copilot-studio:copilot-studio-manage clone` or the VS Code Extension +* [ ] Agents visible in Copilot Chat (`@` menu) or Claude Code (`/` autocomplete) +* [ ] Created a topic with `@copilot-studio:copilot-studio-author` +* [ ] Validated with `@copilot-studio:copilot-studio-advisor` +* [ ] Pulled, pushed, and published (`@copilot-studio:copilot-studio-manage pull`, then `push`) +* [ ] Tested published agent with `@copilot-studio:copilot-studio-test` diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 0000000..4d3987d --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,57 @@ +# Versioning Policy + +This document defines the version synchronization between this fork's VS Code +extension releases and the upstream +[microsoft/skills-for-copilot-studio](https://github.com/microsoft/skills-for-copilot-studio) +releases. + +## Synchronized Versioning + +This fork's extension version **matches the upstream release version**. When +upstream publishes `v1.0.8`, this fork's extension is also released as `v1.0.8`. + +The version is maintained in two places: + +- `extension/templates/package.template.json` — VS Code skills extension +- `extension-pack/package.json` — VS Code extension pack bundle + +Both must always match the upstream version tracked in `upstream-version.json`. + +## Upstream Version Tracking + +The current upstream version is stored in `upstream-version.json` at the +repository root: + +```json +{ + "upstream_repo": "microsoft/skills-for-copilot-studio", + "upstream_version": "v1.0.8", + "last_checked": "2026-04-23" +} +``` + +## Release Workflow Guards + +The following guards enforce the versioning policy: + +1. **Version match** — The `publish-extension.yml` workflow validates that the + extension version matches the upstream version in `upstream-version.json` + before publishing. A release will fail if versions are out of sync. + +2. **Release notes** — Every release includes the upstream version reference + so users can identify compatibility. + +3. **Daily monitor** — The `check-upstream-release.yml` workflow detects new + upstream releases and opens a proposal issue with the recommended action. + +## How to Update When a New Upstream Release Is Detected + +When the daily monitor detects a new upstream release (e.g. `v1.0.9`): + +1. Review the upstream release notes and assess impact. +2. Sync upstream changes using the `sync-upstream.yml` workflow or manually. +3. Update `upstream-version.json` with the new version and date. +4. Update `extension/templates/package.template.json` version to `1.0.9`. +5. Update `extension-pack/package.json` version to `1.0.9`. +6. Test the build locally with `bash extension/test-local.sh --package-only`. +7. Create a release tagged `v1.0.9`. diff --git a/extension-pack/.gitignore b/extension-pack/.gitignore new file mode 100644 index 0000000..27a1f10 --- /dev/null +++ b/extension-pack/.gitignore @@ -0,0 +1 @@ +*.vsix diff --git a/extension-pack/.vscodeignore b/extension-pack/.vscodeignore new file mode 100644 index 0000000..5da8a78 --- /dev/null +++ b/extension-pack/.vscodeignore @@ -0,0 +1,12 @@ +# Exclude everything by default, then include what we need +** + +# Include extension pack essentials +!package.json +!README.md +!LICENSE +!icon.png + +# Exclude build artifacts +*.vsix +build.sh diff --git a/extension-pack/LICENSE b/extension-pack/LICENSE new file mode 100644 index 0000000..cf7bcb2 --- /dev/null +++ b/extension-pack/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extension-pack/README.md b/extension-pack/README.md new file mode 100644 index 0000000..8773bb8 --- /dev/null +++ b/extension-pack/README.md @@ -0,0 +1,33 @@ +## Copilot Studio Development Bundle + +One-click install of everything you need to author, test, and manage [Microsoft Copilot Studio](https://aka.ms/CopilotStudio) agents in VS Code. + +## Included extensions + +| Extension | Publisher | Purpose | +|-----------|-----------|---------| +| [Copilot Studio Skills](https://marketplace.visualstudio.com/items?itemName=coatsy.copilot-studio-skills) | coatsy | YAML authoring, testing, and management skills for GitHub Copilot Chat | +| [Copilot Studio](https://marketplace.visualstudio.com/items?itemName=ms-copilotstudio.vscode-copilotstudio) | Microsoft | Push, pull, clone agents; provides the LSP binary | + +## Prerequisites + +* [VS Code](https://code.visualstudio.com/) 1.106.1 or later +* [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) extension +* [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extension +* [Node.js](https://nodejs.org/) 22+ + +## Getting started + +1. Install this extension pack from the Marketplace +2. Open a workspace containing a Copilot Studio agent (or clone one with `@copilot-studio-manage`) +3. Open Copilot Chat and start working with your agent + +See [SETUP_GUIDE.md](https://github.com/microsoft/skills-for-copilot-studio/blob/main/SETUP_GUIDE.md) for a full end-to-end walkthrough. + +## Disclaimer + +This extension pack is an experimental research project, not an officially supported Microsoft product. The Copilot Studio YAML schema may change without notice. Always review and validate generated YAML before pushing to your environment. + +## License + +[MIT](LICENSE) diff --git a/extension-pack/build.sh b/extension-pack/build.sh new file mode 100644 index 0000000..ad2173e --- /dev/null +++ b/extension-pack/build.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Build and optionally install the Copilot Studio Development Bundle extension pack. +# Usage: ./extension-pack/build.sh [--package-only] +# +# Environment variables: +# CODE_CMD Path to the VS Code CLI (default: auto-detected) +# EXTENSIONS_DIR Custom extensions directory for install (optional) +set -euo pipefail + +PACKAGE_ONLY=false +[[ "${1:-}" == "--package-only" ]] && PACKAGE_ONLY=true + +PACK_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "==> Validating extension pack..." + +# Verify required files exist +for f in package.json README.md icon.png LICENSE; do + if [ ! -f "$PACK_DIR/$f" ]; then + echo "ERROR: Missing required file: $f" + exit 1 + fi +done + +# Validate package.json has extensionPack field +node -e " +const pkg = JSON.parse(require('fs').readFileSync('$(cygpath -m "$PACK_DIR" 2>/dev/null || echo "$PACK_DIR")/package.json', 'utf8')); +if (!pkg.extensionPack || !pkg.extensionPack.length) { + console.error('ERROR: package.json missing extensionPack array'); + process.exit(1); +} +console.log(' Extension pack: ' + pkg.displayName + ' v' + pkg.version); +console.log(' Bundled extensions: ' + pkg.extensionPack.join(', ')); +" + +echo "==> Packaging extension pack..." +cd "$PACK_DIR" +npx --yes @vscode/vsce package --no-dependencies --allow-missing-repository 2>&1 + +VSIX=$(ls -t *.vsix 2>/dev/null | head -1) +if [ -z "$VSIX" ]; then + echo "ERROR: No .vsix file produced" + exit 1 +fi + +if [ "$PACKAGE_ONLY" = true ]; then + echo "" + echo "==> Done! VSIX built at extension-pack/$VSIX" + exit 0 +fi + +echo "" +echo "==> Installing $VSIX..." +CODE_CMD="${CODE_CMD:-$(command -v code 2>/dev/null || echo "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code")}" + +INSTALL_ARGS=(--install-extension "$PACK_DIR/$VSIX") +if [ -n "${EXTENSIONS_DIR:-}" ]; then + INSTALL_ARGS=(--extensions-dir "$EXTENSIONS_DIR" "${INSTALL_ARGS[@]}") +fi + +"$CODE_CMD" "${INSTALL_ARGS[@]}" + +echo "" +echo "==> Done! Reload VS Code to activate the extension pack." +echo " To uninstall: $CODE_CMD --uninstall-extension coatsy.copilot-studio-development-bundle" diff --git a/extension-pack/icon.png b/extension-pack/icon.png new file mode 100644 index 0000000..a05f020 Binary files /dev/null and b/extension-pack/icon.png differ diff --git a/extension-pack/package.json b/extension-pack/package.json new file mode 100644 index 0000000..90dd2d6 --- /dev/null +++ b/extension-pack/package.json @@ -0,0 +1,28 @@ +{ + "name": "copilot-studio-development-bundle", + "displayName": "Copilot Studio Development Bundle", + "version": "1.0.11", + "description": "Extension pack that installs both the Copilot Studio Skills and Copilot Studio extensions for a complete agent development experience", + "publisher": "coatsy", + "repository": { + "type": "git", + "url": "https://github.com/coatsy/skills-for-copilot-studio.git" + }, + "homepage": "https://github.com/coatsy/skills-for-copilot-studio#readme", + "bugs": { + "url": "https://github.com/coatsy/skills-for-copilot-studio/issues" + }, + "engines": { + "vscode": "^1.106.1" + }, + "categories": [ + "Extension Packs" + ], + "icon": "icon.png", + "extensionPack": [ + "ms-copilotstudio.vscode-copilotstudio", + "coatsy.copilot-studio-skills" + ], + "author": "Microsoft", + "license": "MIT" +} diff --git a/extension/CHANGELOG.md b/extension/CHANGELOG.md new file mode 100644 index 0000000..7ace81c --- /dev/null +++ b/extension/CHANGELOG.md @@ -0,0 +1,135 @@ +# Changelog + +## 1.0.11 + +### Added + +- 8 new patterns in the pattern library: + - `chain-of-thought-logging` — log reasoning steps for debugging + - `channel-aware-behavior` — adapt agent responses by channel (Teams, web, etc.) + - `conversation-history-variable` — persist conversation context across turns + - `deterministic-mcp-calls` — ensure reliable MCP tool invocations + - `knowledge-hold-message` — display hold messages during knowledge retrieval + - `line-breaks-in-messages` — control line break rendering in agent messages + - `rai-error-handling` — handle Responsible AI content filter errors gracefully + - `teams-production-hardening` — production readiness checklist for Teams deployments +- `lookup-schema` eval scenario for schema validation testing +- **`int-patterns`** skill expanded with index entries for all new patterns + +### Changed + +- `lookup-schema` skill description updated +- Release branch workflow updated to target next week correctly + +### Upstream Changes + +- Upstream release `v1.0.11` (`release/2026-W19`) + +## 1.0.10 + +### Changed + +- **`troubleshoot`** agent replaced by **`advisor`** agent — the new Advisor agent presents pattern suggestions and validates topics, replacing the former Troubleshoot agent +- **`best-practices`** and **`known-issues`** skills replaced by a unified **pattern library** (`patterns/`) with frontmatter-driven status and an **`int-patterns`** internal skill for index-based routing +- **`authoring-tips`** skill removed; tips content consolidated into the pattern library +- SharePoint knowledge guide updated to reflect runtime end-user permission model (removed references to service account/maker permissions for indexing) +- MCP action metadata updated for `release/2026-W18` +- Skill usage guidelines clarified across agent instructions + +### Added + +- **Pattern library** (`patterns/`) at repo root with 7 standalone pattern files: `date-context`, `dynamic-topic-redirect`, `jit-glossary`, `jit-user-context`, `orchestrator-variables`, `prevent-child-agent-responses`, `prevent-tool-call-leaks` +- **`int-patterns`** internal skill for pattern index lookup and routing + +### Upstream Changes + +- Upstream release `v1.0.10` (`release/2026-W18`) + +## 1.0.9 + +### Changed + +- **`best-practices`** skill split into two focused skills: **`patterns`** (repeatable implementation architectures) and **`authoring-tips`** (practical tips and workarounds) +- Clarified index-first skill routing for pattern and authoring-tip descriptions +- Updated skills count from 28 to 33, adding previously undocumented skills: `analyze-evals`, `create-eval-set`, `run-eval`, `run-tests-kit`, `test-auth` + +### Added + +- **`manage-agent`** / **`clone-agent`** — identify agent from Copilot Studio URL via `--url` flag +- Agent URL parser with comprehensive test suite (`parse-agent-url.test.js`) + +### Fixed + +- Retry transient SSL failures during LSP requests in manage-agent +- Surface LSP error responses from clone and sync operations +- Broken relative link to `orchestrator-variables.md` + +### Upstream Changes + +- Upstream release `v1.0.9` (`release/2026-W17`) + +## 1.0.8 + +### Changed + +- Synchronized extension version with upstream microsoft/skills-for-copilot-studio releases (previously independent at 0.1.x) +- Added daily upstream release monitoring workflow +- Added upstream version badge to README +- Added version synchronization guards to publish workflow + +## 0.1.4 + +### Fixed + +- Updated skills count from 24 to 28 in README, adding missing skills: `chat-directline`, `chat-sdk`, `create-eval`, `detect-mode`, `int-project-context`, `int-reference` + +## 0.1.3 + +### Fixed + +- Build script now resolves skill-local `${CLAUDE_SKILL_DIR}/` path references (not just `../../` patterns), fixing CI validation failures after upstream merges + +### Added + +- Upstream sync workflow (`sync-upstream.yml`) that runs weekly to auto-merge changes from `microsoft/skills-for-copilot-studio` main, creating draft PRs or warning on conflicts +- Dedicated `CLAUDE_SKILL_DIR` resolution check in CI that validates all staged files, not just skills + +### Upstream Changes + +#### Skills + +- **`edit-action`** — now supports MCP server actions (`InvokeExternalAgentTaskAction`) in addition to connector actions; adds SharePoint-specific reference (`sharepoint-actions.md`) with OData filter syntax and quoting patterns +- **`add-action`** — adds MCP server action guidance explaining that MCP connections must be created in the Copilot Studio portal first, plus the new `mcp-action.mcs.yml` template reference +- **`create-eval`** — new skill for self-service eval authoring with scenario-based testing, SHA-256 snapshots, and HTML reports +- **`int-reference`** — adds documentation for `$`-prefixed OData property names (SharePoint `$filter`/`$orderby`) with correct quoting patterns for TaskDialog vs InvokeConnectorAction + +#### Agents + +- **`copilot-studio-author`** — stronger guardrails against creating agent projects from scratch; clearer messaging that `agent.mcs.yml` and `settings.mcs.yml` must not be created, only edited + +#### Templates + +- **`mcp-action.mcs.yml`** — new template for MCP server actions using `InvokeExternalAgentTaskAction` with `ModelContextProtocolMetadata` +- **`connector-action.mcs.yml`** — adds `mcs.metadata` format fields + +#### Scripts + +- Shared auth (`shared-auth.js`) and utilities (`shared-utils.js`) extracted from duplicated script code +- Updated bundles for `chat-with-agent`, `directline-chat`, and `manage-agent` + +## 0.1.2 + +### Fixed + +- Skills referenced in agent body text (e.g., `chat-directline`, `validate`) are now automatically discovered and added to the agent's `skills:` frontmatter during build, making them available in VS Code (#23) +- VS Code build tasks now use Git Bash instead of WSL bash, fixing `node: command not found` errors on Windows systems with WSL + +### Added + +- Build-time validation that warns when `/copilot-studio:*` references don't resolve to a valid skill directory or agent name +- Sub-command validation that checks `/copilot-studio:skill sub-command` patterns against the skill's `argument-hint` frontmatter +- Early exit with a helpful error message when `node` is not found in PATH + +## 0.1.1 + +- Initial release with agents, skills, and extension packaging diff --git a/extension/LICENSE b/extension/LICENSE new file mode 100644 index 0000000..cf7bcb2 --- /dev/null +++ b/extension/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extension/PACKAGING.md b/extension/PACKAGING.md new file mode 100644 index 0000000..33ba188 --- /dev/null +++ b/extension/PACKAGING.md @@ -0,0 +1,213 @@ +--- +title: Extension Packaging Guide +description: "How to build, package, test, and publish the Copilot Studio Skills VS Code extension" +author: Microsoft +ms.date: 2026-03-27 +ms.topic: how-to +--- + +## Overview + +The VS Code extension packages the same agents, skills, scripts, templates, and reference files used by the Claude Code plugin into a `.vsix` file for GitHub Copilot Chat. + +The packaging pipeline is a single Bash script (`extension/test-local.sh`) that stages artifacts, transforms platform-specific constructs, generates `package.json`, and invokes `vsce` to produce the VSIX. + +### Extension structure + +```text +extension/ + .staging/ # Generated staging directory (git-ignored) + docs/adr/ # Architecture decision records + templates/ + package.template.json # Base package.json for the extension + icon.png # Marketplace icon + LICENSE # Extension license + README.md # Marketplace README (rendered on the extension page) + test-local.sh # Build and install script +``` + +After packaging, the staging directory contains the final extension layout: + +```text +.staging/ + agents/ # Agent files renamed to .agent.md + skills/ # Skill files with Claude Code frontmatter stripped + scripts/ # Bundled Node.js scripts (.bundle.js only) + templates/ # YAML templates for common patterns + reference/ # Schema and connector files + package.json # Generated with discovered agents and skills + README.md # Frontmatter-stripped copy of extension/README.md + icon.png # Marketplace icon + LICENSE # License file +``` + +### What the build script does + +1. Copies agent files from `agents/`, renaming `.md` to `.agent.md` (VS Code requires this suffix) +2. Copies skill files from `skills/` and strips Claude Code-specific frontmatter fields (`allowed-tools`, `context`, `agent`, `argument-hint`, `user-invocable`) +3. Copies `scripts/`, `templates/`, and `reference/` directories +4. Generates `package.json` from the template, populating `contributes.chatAgents` and `contributes.chatSkills` by discovering staged files +5. Resolves `${CLAUDE_SKILL_DIR}/../../` path references to relative `../../` paths +6. Strips YAML frontmatter from the extension README (VS Code renders frontmatter as visible text) +7. Packages everything with `vsce` into a `.vsix` file + +## Prerequisites + +| Requirement | Version | Installation | +|-------------|-----------|----------------------------------------------------|| +| Node.js | 22+ | | +| npm | Bundled | Included with Node.js | +| VS Code | 1.106.1+ | | +| Bash | 4+ | Pre-installed on macOS/Linux; use Git Bash on Windows | + +The `vsce` CLI is fetched automatically via `npx` during packaging. No global install is needed. + +## Build and package + +### Package the extension + +Run the build script with the `--package-only` flag to produce the VSIX without installing: + +```bash +bash extension/test-local.sh --package-only +``` + +The VSIX file is written to `extension/copilot-studio-skills-.vsix`. + +### Build and install locally + +Run the script without flags to package and install in one step: + +```bash +bash extension/test-local.sh +``` + +This packages the extension and then runs `code --install-extension` to install it. Reload VS Code to activate. + +> [!TIP] +> Set the `CODE_CMD` environment variable to point to a specific VS Code binary if `code` is not on your PATH: +> +> ```bash +> CODE_CMD="/path/to/code" bash extension/test-local.sh +> ``` + +### Uninstall + +```bash +code --uninstall-extension coatsy.copilot-studio-skills +``` + +## Development debugging + +For deeper debugging, build VS Code from source and use it as an isolated extension host. This gives you a sandboxed environment free from other extensions and user-specific settings. + +See [Local Dev Host Setup Guide](docs/LOCAL_DEV_HOST.md) for the full walkthrough, including: + +- Building VS Code from source +- Launching with isolated `--user-data-dir` and `--extensions-dir` +- Automated setup via `extension/setup-devhost.sh` + +Point `test-local.sh` at the dev build by setting `CODE_CMD` to the dev instance's CLI binary: + +```bash +CODE_CMD="/path/to/vscode/scripts/code-cli.sh" \ + bash extension/test-local.sh +``` + +For launch.json configurations and debugger attachment, see [Debug Configuration Reference](docs/DEBUG_CONFIG.md). + +## CI/CD pipeline + +The GitHub Actions workflow at `.github/workflows/build-extension.yml` runs on every push and pull request that touches agent, skill, script, template, reference, or extension files. It can also be triggered manually via `workflow_dispatch` with an optional version override. + +The pipeline: + +1. Checks out the repository +2. Sets up Node.js 22 +3. Applies the version override if provided (manual dispatch only) +4. Runs `bash extension/test-local.sh --package-only` (with `CODE_CMD=true` to skip VS Code install) +5. Verifies a VSIX file was produced +6. Validates VSIX contents (agent/skill counts, no Claude-specific fields, required directories) +7. Uploads the VSIX as a build artifact (retained for 30 days) + +A separate publish workflow at `.github/workflows/publish-extension.yml` handles Marketplace publishing. It triggers on GitHub Release creation or manual dispatch and supports a dry-run mode for testing without publishing. + +## Publishing to the Marketplace + +> [!IMPORTANT] +> The extension publisher is set to `coatsy` in `extension/templates/package.template.json`. + +### First-time setup + +1. Create a publisher on the [VS Code Marketplace](https://marketplace.visualstudio.com/manage) +2. Generate a Personal Access Token (PAT) with the **Marketplace (Manage)** scope: + 1. Go to [Azure DevOps](https://dev.azure.com) + 2. Open **User settings** (top-right gear) → **Personal access tokens** → **New Token** + 3. Set the organization to **All accessible organizations** + 4. Under **Scopes**, select **Custom defined** and check **Marketplace > Manage** + 5. Copy the generated token +3. Update `publisher` in `extension/templates/package.template.json` to match your publisher ID + +### Setting up `VSCE_PAT` for CI/CD + +The publish workflow (`.github/workflows/publish-extension.yml`) requires a `VSCE_PAT` repository secret: + +1. Generate a PAT using the steps above +2. In your GitHub repository, go to **Settings** → **Secrets and variables** → **Actions** +3. Click **New repository secret** +4. Name: `VSCE_PAT`, Value: paste the PAT +5. Click **Add secret** + +The publish workflow triggers automatically when a GitHub Release is created. Manual dispatch is also available with an optional dry-run mode. + +### Publish manually + +```bash +# Package first +bash extension/test-local.sh --package-only + +# Publish the VSIX +cd extension/.staging +npx @vscode/vsce publish --pat +``` + +Alternatively, upload the VSIX manually through the [Marketplace management portal](https://marketplace.visualstudio.com/manage). + +### Publish via CI/CD + +The automated workflow handles publishing on release: + +1. Update the `version` in `extension/templates/package.template.json` +2. Commit and push to the default branch +3. Create a GitHub Release with a tag matching the version (e.g., `v0.2.0`) +4. The workflow builds the VSIX, validates the version matches, and publishes + +To test without publishing, use the **Run workflow** button on the Actions tab and check the **Dry run** option. + +## Version management + +The extension version is defined in `extension/templates/package.template.json` in the `version` field. + +To bump the version: + +1. Update the `version` field in `extension/templates/package.template.json` +2. Rebuild with `bash extension/test-local.sh --package-only` +3. Commit the template change + +The version follows [SemVer](https://semver.org/): + +* Increment the **major** version for breaking changes to agent or skill interfaces +* Increment the **minor** version when adding new agents, skills, or templates +* Increment the **patch** version for bug fixes and documentation updates + +## Troubleshooting + +| Issue | Cause | Solution | +|---------------------------------|--------------------------------------------|---------------------------------------------------------------------------| +| `No .vsix file produced` | `vsce` packaging failed | Check the script output for errors; verify Node.js 22+ is installed | +| Extension not visible in Chat | Extension not activated after install | Reload VS Code (`Developer: Reload Window`) | +| Agents or skills missing | Files not discovered during staging | Verify agent files exist in `agents/` and skill folders contain `SKILL.md` | +| `icon` field error from `vsce` | `icon.png` missing from `extension/` | Add an `icon.png` or remove the `icon` field from the template | +| Frontmatter visible in README | YAML frontmatter not stripped | Re-run `test-local.sh` to regenerate the staged README | +| `command not found: code` | VS Code CLI not on PATH | Set `CODE_CMD` or install the `code` command from VS Code Command Palette | +| Publisher rejected | Publisher ID not registered or mismatched | Verify `publisher` in `package.template.json` matches your Marketplace account | diff --git a/extension/README.md b/extension/README.md new file mode 100644 index 0000000..354b6a1 --- /dev/null +++ b/extension/README.md @@ -0,0 +1,66 @@ +--- +title: Copilot Studio Skills +description: "Copilot Studio YAML authoring, testing, and management skills for GitHub Copilot Chat" +--- + +## Copilot Studio Skills for GitHub Copilot + +Author, test, troubleshoot, and manage [Microsoft Copilot Studio](https://aka.ms/CopilotStudio) agents directly from VS Code using GitHub Copilot Chat. + +This extension provides **4 specialized agents** and **33 skills** that enable YAML-based authoring of Copilot Studio agents without leaving your editor. + +## Features + +### Agents + +Use these agents in Copilot Chat to get specialized help: + +* **Copilot Studio Author** — Create and edit topics, actions, knowledge sources, child agents, and global variables +* **Copilot Studio Manage** — Clone, push, pull, and sync agent content between local YAML files and the Power Platform cloud +* **Copilot Studio Test** — Test published agents with point-tests, batch test suites, DirectLine, or evaluation analysis +* **Copilot Studio Troubleshoot** — Debug issues including wrong topic routing, validation errors, and unexpected behavior + +### Skills + +The extension includes 33 skills covering the full agent development lifecycle: + +| Category | Skills | +|----------|--------| +| **Authoring** | new-topic, add-action, add-node, add-knowledge, add-adaptive-card, add-generative-answers, add-global-variable, add-other-agents | +| **Editing** | edit-agent, edit-action, edit-triggers | +| **Validation** | validate, lookup-schema, list-kinds, list-topics | +| **Testing** | chat-with-agent, chat-directline, chat-sdk, directline-chat, run-tests-kit, test-auth, create-eval, create-eval-set, detect-mode | +| **Evaluation** | analyze-evals, run-eval | +| **Management** | manage-agent, clone-agent | +| **Patterns & Tips** | patterns, authoring-tips | +| **Troubleshooting** | known-issues | +| **Internal** | int-project-context, int-reference | + +## Prerequisites + +* [VS Code](https://code.visualstudio.com/) 1.106.1 or later +* [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) extension +* [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) extension +* [Copilot Studio VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-copilotstudio.vscode-copilotstudio) (required for push/pull/clone operations) +* [Node.js](https://nodejs.org/) 22+ + +## Quick start + +1. Install the extension +2. Open a workspace containing a Copilot Studio agent (or clone one using the Manage agent) +3. Open Copilot Chat and ask a question about your agent + +```text +@copilot-studio-author Create a topic that handles IT service requests +@copilot-studio-manage Clone an agent from my environment +@copilot-studio-test Send "How do I request a new laptop?" to the published agent +@copilot-studio-troubleshoot The agent is not using data from our knowledge base +``` + +## Disclaimer + +This extension is an experimental research project, not an officially supported Microsoft product. The Copilot Studio YAML schema may change without notice. Always review and validate generated YAML before pushing to your environment. + +## License + +[MIT](LICENSE) diff --git a/extension/docs/DEBUG_CONFIG.md b/extension/docs/DEBUG_CONFIG.md new file mode 100644 index 0000000..f5f544a --- /dev/null +++ b/extension/docs/DEBUG_CONFIG.md @@ -0,0 +1,120 @@ +--- +title: "Debug Configuration Reference" +description: "VS Code launch.json configurations for debugging the Copilot Studio Skills extension in a self-hosted dev build" +author: Microsoft +ms.date: 2026-03-28 +ms.topic: reference +--- + +This document provides `launch.json` configurations for debugging the Copilot Studio Skills extension using a self-hosted VS Code build. For instructions on building VS Code from source, see [LOCAL_DEV_HOST.md](LOCAL_DEV_HOST.md). + +> [!NOTE] +> The Copilot Studio Skills extension contributes chat agents and skills. To test chat functionality in the Extension Development Host, install [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) and [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) in the dev host's extensions directory. See [required extensions](LOCAL_DEV_HOST.md#required-extensions-for-chat-integration) for details. + +## Launch configurations + +Add these entries to `.vscode/launch.json` in the `skills-for-copilot-studio` repository. + +### Launch Extension Development Host (self-hosted) + +This configuration launches the Extension Development Host using the locally built VS Code instance with isolated user data and extensions directories: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Extension (Dev Host)", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${env:VSCODE_DEV_PATH}/scripts/code-cli.sh", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/extension/.staging", + "--user-data-dir=${workspaceFolder}/.vscode-dev-data", + "--extensions-dir=${workspaceFolder}/.vscode-dev-extensions" + ], + "outFiles": [ + "${workspaceFolder}/extension/.staging/**/*.js" + ], + "preLaunchTask": "Package Extension" + } + ] +} +``` + +Set the `VSCODE_DEV_PATH` environment variable to the absolute path of your local VS Code clone (for example, `~/repos/vscode` or `C:\repos\vscode`). + +> [!TIP] +> On Windows, replace `code-cli.sh` with `code-cli.bat` in the `runtimeExecutable` path. + +### Attach to Extension Host process + +Use this configuration to attach the debugger to an already-running Extension Development Host: + +```json +{ + "name": "Attach to Extension Host", + "type": "node", + "request": "attach", + "port": 5870, + "restart": true, + "outFiles": [ + "${workspaceFolder}/extension/.staging/**/*.js" + ] +} +``` + +To use this, launch the dev host with the `--inspect-extensions=5870` flag: + +```bash +bash scripts/code.sh --user-data-dir .vscode-dev-data \ + --extensions-dir .vscode-dev-extensions \ + --inspect-extensions=5870 +``` + +### Launch with stable VS Code (no dev build) + +For quick testing without a self-hosted build, this configuration uses the standard VS Code binary: + +```json +{ + "name": "Launch Extension (Stable)", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}/extension/.staging", + "--user-data-dir=${workspaceFolder}/.vscode-dev-data", + "--extensions-dir=${workspaceFolder}/.vscode-dev-extensions" + ], + "outFiles": [ + "${workspaceFolder}/extension/.staging/**/*.js" + ], + "preLaunchTask": "Package Extension" +} +``` + +Both launch configurations reference the "Package Extension" task defined in `.vscode/tasks.json`, which runs `test-local.sh --package-only` to stage the extension before the debug session starts. + +## Setting breakpoints + +### Skill and agent resolution + +The extension resolves skills and agents at startup by scanning the `skills/` and `agents/` directories. To debug resolution: + +1. Open the bundled script files under `extension/.staging/scripts/`. +2. Set breakpoints on file-system read operations that scan for `SKILL.md` or `.agent.md` files. +3. Launch the Extension Development Host and trigger agent discovery by opening Copilot Chat. + +### Copilot Chat integration + +To inspect how the extension registers participants with GitHub Copilot Chat: + +1. Set breakpoints in the `package.json` generation logic in `test-local.sh` (the Node.js inline scripts). +2. After installing, set breakpoints in the staged extension code that handles chat participant registration. +3. Open Copilot Chat and type `@` to trigger participant enumeration. + +## Tips + +- Use `--disable-extensions` in launch args to run without other extensions, isolating the debug session to only the Copilot Studio Skills extension. +- The `--user-data-dir` and `--extensions-dir` flags prevent the debug session from interfering with your primary VS Code configuration. +- When debugging native module issues, add `"env": {"NODE_DEBUG": "module"}` to the launch configuration to trace module resolution. diff --git a/extension/docs/LOCAL_DEV_HOST.md b/extension/docs/LOCAL_DEV_HOST.md new file mode 100644 index 0000000..569120c --- /dev/null +++ b/extension/docs/LOCAL_DEV_HOST.md @@ -0,0 +1,206 @@ +--- +title: "Local Dev Host Setup Guide" +description: "Build VS Code from source and use it as an isolated extension debug host for the Copilot Studio Skills extension" +author: Microsoft +ms.date: 2026-03-28 +ms.topic: how-to +--- + +This guide walks you through building VS Code from source and using that self-hosted instance as an isolated debug environment for the Copilot Studio Skills extension. + +A self-hosted build avoids cross-contamination with your primary VS Code installation. Extension state, settings, and other extensions remain separate, giving you a clean environment for reproducing issues and stepping through integration code. + +## Prerequisites + +| Requirement | Version | Notes | +|-------------|---------|-------| +| Git | 2.x+ | `git --version` | +| Git LFS | 2.x+ | `git lfs version`; required by the VS Code repo for binary assets | +| Node.js | 22+ | `node --version`; check `vscode/.nvmrc` for the exact minor version | +| Python | 3.11+ | Required by native module compilation (node-gyp) | +| C++ toolchain | Platform-specific | See [platform notes](#platform-notes) | +| Disk space | ~10 GB | VS Code source, dependencies, and build output | + +## Platform notes + +### Windows + +Install Visual Studio 2022 Build Tools with the "Desktop development with C++" workload. Node-gyp v11+ (bundled with Node 22) requires VS 2022; VS 2019 is not sufficient. + +```powershell +# Install via winget (installs the base shell only) +winget install Microsoft.VisualStudio.2022.BuildTools + +# Then add the C++ workload via the VS Installer CLI (run as admin) +& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" modify ` + --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools" ` + --add Microsoft.VisualStudio.Workload.VCTools ` + --add Microsoft.VisualStudio.Component.VC.Spectre.x86.x64 ` + --includeRecommended --passive +``` + +Or open the Visual Studio Installer UI, select Build Tools 2022, click Modify, and check "Desktop development with C++" plus "MSVC v143 Spectre-mitigated libs" in Individual Components. + +### macOS + +Install Xcode Command Line Tools: + +```bash +xcode-select --install +``` + +### Linux (Debian/Ubuntu) + +```bash +sudo apt-get install -y build-essential g++ libx11-dev \ + libxkbfile-dev libsecret-1-dev libkrb5-dev +``` + +See the VS Code [build prerequisites wiki page](https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites) for the full list. + +## Step-by-step setup + +### 1. Clone VS Code + +The VS Code repository uses Git LFS for binary assets (screenshots, test fixtures). Install Git LFS before cloning: + +```bash +# Install Git LFS (one-time setup) +git lfs install + +# Clone the repo +git clone https://github.com/microsoft/vscode.git +cd vscode +``` + +> [!TIP] +> Use `--depth 1` for a shallow clone if you only need the latest commit and want a faster download. + +> [!TIP] +> If Git LFS downloads are slow or you do not need the binary test fixtures, skip LFS during clone and pull them later if needed: +> +> ```bash +> GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1 https://github.com/microsoft/vscode.git +> ``` + +### 2. Install dependencies + +```bash +npm install +``` + +This can take several minutes on first run. If native module compilation fails, check the [troubleshooting](#troubleshooting) section. + +### 3. Build in watch mode + +```bash +npm run watch +``` + +Leave this running in a separate terminal. It recompiles automatically when VS Code source files change. + +### 4. Launch the dev instance + +Open a new terminal and run: + +```bash +bash scripts/code.sh +``` + +This starts a self-hosted VS Code instance built from your local source. + +### 5. Isolate user data + +To keep the dev instance completely separate from your primary VS Code, specify custom directories: + +```bash +bash scripts/code.sh --user-data-dir .vscode-dev-data \ + --extensions-dir .vscode-dev-extensions +``` + +This prevents the dev instance from reading or modifying any settings, state, or extensions from your primary installation. + +## Loading the extension in the dev host + +### Option A: Build and install with test-local.sh + +Use the `CODE_CMD` environment variable to point `test-local.sh` at the dev build's CLI. When using a custom `--extensions-dir`, also set `EXTENSIONS_DIR` so the extension installs into the same directory the dev host reads from: + +```bash +# From the skills-for-copilot-studio repo root +CODE_CMD="/path/to/vscode/scripts/code-cli.sh" \ + EXTENSIONS_DIR=".vscode-dev-extensions" \ + bash extension/test-local.sh +``` + +On Windows (Git Bash): + +```bash +CODE_CMD="/c/path/to/vscode/scripts/code-cli.bat" \ + EXTENSIONS_DIR=".vscode-dev-extensions" \ + bash extension/test-local.sh +``` + +The dev instance's `code` CLI is located at: + +| Platform | Path | +|----------------|---------------------------------| +| macOS / Linux | `/scripts/code-cli.sh` | +| Windows | `/scripts/code-cli.bat` | + +> [!IMPORTANT] +> The dev build CLI resolves relative paths from the `vscode/` directory, not from your working directory. Use **absolute paths** for both `CODE_CMD` and the VSIX file, or set `EXTENSIONS_DIR` to let `test-local.sh` handle path resolution. +> +> Without `EXTENSIONS_DIR`, the extension installs to `~/.vscode-oss-dev/extensions/` — which is ignored when the dev host is launched with a custom `--extensions-dir`. + +### Required extensions for chat integration + +The Copilot Studio Skills extension contributes chat agents and skills that require GitHub Copilot Chat to be present. Install these extensions in the dev host before testing chat functionality: + +* [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) +* [GitHub Copilot Chat](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot-chat) + +You can install them via the dev host CLI: + +```bash +/path/to/vscode/scripts/code-cli.sh \ + --extensions-dir .vscode-dev-extensions \ + --install-extension GitHub.copilot \ + --install-extension GitHub.copilot-chat +``` + +### Option B: Launch via launch.json (F5) + +See [DEBUG_CONFIG.md](DEBUG_CONFIG.md) for launch configurations that start an Extension Development Host backed by the self-hosted VS Code build. + +## Using the automated setup script + +A helper script automates the clone, dependency install, build, and launch steps: + +```bash +bash extension/setup-devhost.sh +``` + +The script is idempotent. If the VS Code repo already exists and dependencies are installed, it skips those steps and goes straight to launching the dev instance. Use `--help` for available flags: + +```bash +bash extension/setup-devhost.sh --help +``` + +See [setup-devhost.sh](../setup-devhost.sh) for the full source. + +## Troubleshooting + +| Issue | Possible cause | Solution | +|-------|----------------|----------| +| `gyp ERR! find Python` | Python not found or wrong version | Install Python 3.11+ and ensure it is on your PATH | +| `gyp ERR! find VS` (Windows) | Missing C++ build tools | Install VS 2022 Build Tools with the "Desktop development with C++" workload. Node 22's node-gyp requires VS 2022. | +| `error MSB8040: Spectre-mitigated libraries` | Missing Spectre libs in VS 2022 | Add `Microsoft.VisualStudio.Component.VC.Spectre.x86.x64` via the VS Installer (Individual Components tab) | +| `node-gyp` compilation errors (macOS) | Missing Xcode tools | Run `xcode-select --install` | +| `ENOMEM` or out-of-memory during build | Not enough RAM | Close other applications or increase swap space | +| `npm run watch` never finishes | Expected behavior | `npm run watch` runs continuously; open a new terminal for `bash scripts/code.sh` | +| Extension not visible after install | Dev instance not using the right extensions dir | Pass `EXTENSIONS_DIR` to `test-local.sh` or use `--extensions-dir` with `code-cli.sh`; see [Loading the extension](#loading-the-extension-in-the-dev-host) | +| Agents not visible in Copilot Chat | GitHub Copilot Chat not installed in the dev host | Install Copilot and Copilot Chat extensions in the dev host; see [required extensions](#required-extensions-for-chat-integration) | +| `git-lfs: command not found` during clone | Git LFS not installed | Install Git LFS: `git lfs install`. On Windows, install via `winget install GitHub.GitLFS` | +| Clone warns "checkout failed" | Git LFS filter missing from PATH | Ensure `git-lfs` is on your PATH, then run `git lfs install && git restore --source=HEAD :/` inside the clone | +| `code-cli.sh: command not found` | Wrong path to the dev build CLI | Check that the path points to the `scripts/` directory inside the VS Code repo | diff --git a/extension/docs/adr/001-artifact-file-strategy.md b/extension/docs/adr/001-artifact-file-strategy.md new file mode 100644 index 0000000..4b73136 --- /dev/null +++ b/extension/docs/adr/001-artifact-file-strategy.md @@ -0,0 +1,227 @@ +--- +title: "ADR-001: Shared vs VS Code-Specific Artifact Files" +description: "Architecture decision record evaluating whether to create VS Code-specific versions of agent and skill files or share them with the Claude Code plugin" +author: @coatsy +ms.date: 2026-03-27 +ms.topic: concept +--- + +## Status + +Accepted + +## Context + +The repository contains 4 agent files and 24 skill files originally authored for the +Claude Code plugin. A VS Code extension (issue #1) must register these same artifacts +as `contributes.chatAgents` and `contributes.chatSkills` entries. + +The agent and skill files contain platform-specific constructs at two levels: + +### Frontmatter divergence + +| Field | Occurrence | Platform | +|-------|-----------|----------| +| `name` | 4 agents, 3 skills | Both (portable) | +| `description` | All | Both (portable) | +| `skills` | 4 agents | Both (portable) | +| `user-invocable` | 24 skills | Unknown — all set to `false` | +| `allowed-tools` | 21/24 skills | Claude Code only (e.g., `Bash(node *schema-lookup.bundle.js *)`) | +| `context: fork` | 18/24 skills | Claude Code only (subprocess isolation) | +| `agent` | 18/24 skills | Claude Code only (agent routing) | +| `argument-hint` | 17/24 skills | Unknown | + +### Body divergence + +* 19/24 skills reference `${CLAUDE_SKILL_DIR}` (a Claude Code path variable) in + bash code blocks to locate bundled scripts. Total: 76 references across all files. +* 18/24 skills contain bash command patterns (```` ```bash ````) that invoke + Node.js scripts via Claude Code's `Bash()` tool. +* Agent bodies are fully portable — zero Claude Code-specific constructs. + +### File extension requirement + +VS Code `contributes.chatAgents` requires `.agent.md` file extensions. Current agent +files use plain `.md`. + +## Decision + +We need to determine the artifact file strategy for dual-platform support. + +## Options Considered + +### Option A: Single shared files (current approach) + +Keep one set of agent and skill files. Rename agents from `.md` to `.agent.md`. +Rely on each platform ignoring unknown frontmatter fields. + +**Advantages:** + +* Single source of truth — no sync or drift risk +* Minimal file count (4 agents + 24 skills unchanged) +* Simple build pipeline — discover and register as-is +* Agent files are already fully portable + +**Disadvantages:** + +* Assumes VS Code ignores unknown frontmatter fields (`allowed-tools`, + `context: fork`, `agent`) — untested +* `${CLAUDE_SKILL_DIR}` references in 19 skill bodies won't resolve in VS Code +* If VS Code errors on unknown frontmatter, all 21 affected skills break +* Renaming `.md` → `.agent.md` may break Claude Code plugin (needs testing) +* No opportunity to tailor instructions per platform + +**Risk:** Medium — hinges on the untested assumption that VS Code silently ignores +unknown YAML frontmatter fields. + +### Option B: VS Code-specific copies at build time + +Maintain the original files for Claude Code. At build time, generate VS Code-specific +copies that strip incompatible frontmatter and replace `${CLAUDE_SKILL_DIR}` paths. + +**Advantages:** + +* Zero risk to Claude Code — originals untouched +* Clean VS Code experience — no unknown frontmatter noise +* Can tailor instructions per platform (e.g., replace `Bash()` tool references + with VS Code tool equivalents) +* Agent `.md` → `.agent.md` copy happens only in the build output + +**Disadvantages:** + +* Two versions of every file — drift risk if the transform is incomplete or buggy +* Build script complexity — must parse frontmatter, strip fields, rewrite paths +* 24 skill files + 11 supporting files + 4 agent files to transform (39 files) +* Transform logic must be maintained as new frontmatter fields are added +* Developers must remember to edit the source files, not the generated copies + +**Risk:** Medium — the transform script becomes a maintenance burden and a source +of subtle bugs. + +### Option C: VS Code-specific files maintained in parallel + +Create a separate `extension/agents/` and `extension/skills/` directory tree with +hand-maintained VS Code-specific versions of each file. + +**Advantages:** + +* Full control over each platform's experience +* Can optimize instructions, examples, and tool references per platform +* No build-time transform — files are ready to package + +**Disadvantages:** + +* 39+ files duplicated and maintained in parallel +* High drift risk — changes to skill logic must be applied in two places +* Doubles the review surface for every skill change +* 2,774 lines of skill content (plus 11 supporting files) to keep synchronized +* No automation to detect when files diverge + +**Risk:** High — parallel maintenance of ~40 files with no sync mechanism virtually +guarantees divergence. + +### Option D: Shared files with platform overrides + +Keep shared files as the single source. Create a small set of VS Code-specific +override files only where the platforms genuinely diverge. The build script merges +overrides into the shared base. + +**Advantages:** + +* Single source of truth for the 95% of content that is portable +* Override files are small — only the delta (frontmatter patches, path rewrites) +* Lower drift risk than full copies — overrides are explicit and reviewable +* Scales well — only files with actual incompatibilities need overrides +* Agent files need zero overrides (already portable) + +**Disadvantages:** + +* Requires a merge mechanism in the build script +* Override format must be well-defined and documented +* Adds conceptual complexity — contributors must understand the override system +* Still needs testing to confirm VS Code handles the merged output correctly + +**Risk:** Low-medium — more engineering than Option A, but much less maintenance +than Options B or C. + +### Option E: Shared files with frontmatter-only stripping + +Keep shared files as the single source. At build time, strip only the known +incompatible frontmatter fields. Leave body content (including `${CLAUDE_SKILL_DIR}`) +unchanged and document it as a known limitation. + +**Advantages:** + +* Simplest build transform — a frontmatter-only pass, no body rewriting +* Single source of truth for all content +* Agent files need no transform (already portable) +* Only 5 frontmatter fields to strip: `allowed-tools`, `context`, `agent`, + `argument-hint`, `user-invocable` +* `${CLAUDE_SKILL_DIR}` limitation is acceptable short-term — VS Code Copilot Chat + doesn't execute bash commands directly from skill files anyway + +**Disadvantages:** + +* Skill bodies still contain Claude Code-specific bash patterns — may confuse + VS Code users reading skill instructions +* `${CLAUDE_SKILL_DIR}` paths are visible but non-functional in VS Code +* Still requires a build step (though minimal) + +**Risk:** Low — the transform is simple enough to be reliable, and the body content +limitation is acceptable for the initial release. + +## Analysis + +The key factors are: + +1. **Agent files are portable today** — all 4 files have zero Claude Code-specific + constructs. Only the file extension (`.md` → `.agent.md`) needs to change. + +2. **Skill frontmatter is the main incompatibility** — 21/24 skills use Claude + Code-specific fields. Whether VS Code ignores them is untested. + +3. **Skill body content is informational** — VS Code Copilot Chat reads skill bodies + as context/instructions for the LLM, but doesn't execute bash commands directly. + The `${CLAUDE_SKILL_DIR}` references are guidance for the AI, not runtime code. + They may confuse the model, but they won't cause errors. + +4. **Maintenance cost dominates** — Options B and C create ongoing sync burdens + that outweigh the cleanliness benefit. The team is small and changes to skills + are frequent. + +5. **Incremental approach is safest** — start with the lowest-risk option, validate + assumptions, and escalate only if problems emerge. + +## Recommendation + +**Option E: Shared files with frontmatter-only stripping.** + +This provides the best balance of safety, simplicity, and maintainability: + +1. **Copy and rename** agent files from `.md` to `.agent.md` at build time into a + transient staging directory. Source files remain `.md` in the repository, + preserving Claude Code compatibility without any testing or risk. + +2. **Build script strips** these frontmatter fields from skill file copies when + packaging for VS Code: `allowed-tools`, `context`, `agent`, `argument-hint`, + `user-invocable`. Source skill files are never modified. + +3. **Document** `${CLAUDE_SKILL_DIR}` in skill bodies as a known limitation. + The VS Code Copilot Chat LLM will see these references but they serve as + documentation of what scripts exist — the LLM can still use this context + to guide users, even if it cannot execute the commands directly. + +4. **Revisit** if VS Code introduces its own tool-restriction or execution model + that requires body-level changes — at that point, upgrade to Option D + (platform overrides) for the affected skills only. + +## Consequences + +* The build script uses a transient staging directory for all transformations — + source files are never modified +* Agent `.md` → `.agent.md` renaming happens only in the staging copy +* Skill frontmatter stripping happens only in the staging copy (~20 lines of Node.js) +* All skill and agent content is maintained in one place +* Contributors edit files in `agents/` and `skills/` only — never in build output +* `${CLAUDE_SKILL_DIR}` references remain in VS Code skill files until a VS Code + equivalent exists or the body divergence justifies per-file overrides diff --git a/extension/docs/adr/002-dev-host-tooling-location.md b/extension/docs/adr/002-dev-host-tooling-location.md new file mode 100644 index 0000000..5a078ba --- /dev/null +++ b/extension/docs/adr/002-dev-host-tooling-location.md @@ -0,0 +1,95 @@ +--- +title: "ADR-002: Dev Host Tooling Location" +description: "Architecture decision record for where to place the VS Code self-hosted build tooling and documentation" +author: "@coatsy" +ms.date: 2026-03-28 +ms.topic: concept +--- + +## Status + +Accepted + +## Context + +Issue #14 introduces documentation and a helper script for building VS Code from source and using it as an isolated extension debug host. The issue itself raised the question of where this tooling should live: + +- A separate repository (for example, `skills-for-copilot-studio-devhost`) with its own build scripts and a submodule reference to VS Code. +- A `dev/` or `tools/` directory in this repository with helper scripts and documentation. +- The existing `extension/` directory tree, colocated with the packaging and build workflow. + +## Decision + +Keep the dev host tooling in the `extension/` directory of this repository. + +## Options Considered + +### Option A: Separate repository + +Create a new `skills-for-copilot-studio-devhost` repository containing the VS Code clone instructions, build scripts, and debug configurations. + +**Advantages:** + +- Clean separation of concerns; the main repo stays focused on skills and agents +- Independent versioning and CI for the devhost setup +- Contributors who only author skills never see devhost files + +**Disadvantages:** + +- Introduces a second repo to clone, sync, and maintain +- Debug configurations (launch.json) reference paths in both repos, creating fragile cross-repo dependencies +- Higher friction for new contributors: two repos to discover, clone, and coordinate +- The devhost setup is tightly coupled to the extension's `test-local.sh` and `CODE_CMD` mechanism, making cross-repo references unavoidable + +**Risk:** Medium. The coupling between the devhost setup and the extension build process means a separate repo would require constant cross-referencing, negating the isolation benefit. + +### Option B: Top-level `dev/` or `tools/` directory + +Place scripts and documentation under a new top-level directory such as `dev/` or `tools/`. + +**Advantages:** + +- Visible at the repo root; easy to discover +- Keeps `extension/` focused on packaging artifacts + +**Disadvantages:** + +- Introduces a new top-level directory with a narrow purpose (one script, two docs) +- Debug configurations and the helper script need to reference `extension/test-local.sh` with relative paths that cross directory boundaries +- Inconsistent with the existing pattern where extension-related files live under `extension/` + +**Risk:** Low, but adds organizational noise for a small number of files. + +### Option C: Inside `extension/` (selected) + +Place documentation under `extension/docs/` (where ADRs already live) and the helper script at `extension/setup-devhost.sh` alongside `extension/test-local.sh`. + +**Advantages:** + +- Colocated with the build script it complements (`test-local.sh`) +- Follows the existing pattern: extension docs are in `extension/docs/`, extension scripts are in `extension/` +- No new top-level directories; minimal change to repo structure +- Cross-references between the devhost script and `test-local.sh` use simple sibling paths +- Debug configurations naturally live in the repo's `.vscode/launch.json` + +**Disadvantages:** + +- Extension directory grows slightly (one script, two docs) +- Contributors browsing `extension/` see devhost files even if they will not use them + +**Risk:** Low. The additional files are small and clearly named. + +## Analysis + +The dev host setup is a direct extension of the existing `test-local.sh` workflow. It shares the same `CODE_CMD` mechanism, processes the same `.staging/` output, and targets the same VSIX. Separating it into a different location would create cross-cutting references without meaningful isolation. + +The `extension/docs/` directory already holds architecture decision records, making it a natural home for setup guides. The helper script (`setup-devhost.sh`) is analogous to `test-local.sh` and belongs alongside it. + +The file count is small (two markdown files, one shell script), so a dedicated directory or repository would be over-engineering. + +## Consequences + +- `extension/docs/LOCAL_DEV_HOST.md` and `extension/docs/DEBUG_CONFIG.md` live alongside ADRs +- `extension/setup-devhost.sh` sits next to `extension/test-local.sh` +- `.gitignore` entries cover the default dev host isolation directories (`.vscode-dev-data/`, `.vscode-dev-extensions/`, `vscode/`) +- If the dev host tooling grows significantly in the future (multiple scripts, platform-specific installers), this decision should be revisited in a follow-up ADR diff --git a/extension/docs/blog/agent-chooser.png b/extension/docs/blog/agent-chooser.png new file mode 100644 index 0000000..6a209d2 Binary files /dev/null and b/extension/docs/blog/agent-chooser.png differ diff --git a/extension/docs/blog/blog-post.md b/extension/docs/blog/blog-post.md new file mode 100644 index 0000000..f2fb3e5 --- /dev/null +++ b/extension/docs/blog/blog-post.md @@ -0,0 +1,532 @@ +# Bringing Copilot Studio to VS Code: How We Built a Cross-Platform Agent Toolkit with GitHub Copilot Chat + + + + + +Developers who build Microsoft Copilot Studio agents spend most of their time in VS Code. But the Copilot Studio authoring experience lives in a web browser. I wanted to close that gap — and do it in a way that lets GitHub Copilot Chat be the interface for creating, testing, and managing agents, all from the editor. + +The result is an open-source toolkit called [Skills for Copilot Studio](https://github.com/microsoft/skills-for-copilot-studio). It provides **4 specialized agents** and **24 skills** that bring the full Copilot Studio agent development lifecycle into VS Code through GitHub Copilot Chat. You can clone an agent from the cloud, author new topics in YAML, validate them against the real schema, push, publish, and test — without leaving your editor. + +The toolkit started as a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin built by the Copilot Studio Customer Advisory Team (CAT). I then extracted it into a VS Code extension that packages the same agents and skills for GitHub Copilot Chat, using a build-time transformation pipeline and CI/CD automation. This post covers what the toolkit does, how the extraction works, and how we test and ship it. + +![VS Code with GitHub Copilot Chat showing the Copilot Studio Author agent creating a new topic YAML file](./hero-screenshot.png) + +## A quick word on Microsoft Copilot Studio + +[Microsoft Copilot Studio](https://learn.microsoft.com/microsoft-copilot-studio/fundamentals-what-is-copilot-studio) is a low-code platform for building AI agents on the Power Platform. Agents are composed of **topics** (conversation flows with triggers and actions), **knowledge sources** (websites, SharePoint, Dataverse), **actions** (connectors to external services like Teams and Outlook), and **global variables** for state management. + +Under the hood, every agent is defined in YAML files with the `.mcs.yml` extension. The official [Copilot Studio VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-copilotstudio.vscode-copilotstudio) lets you clone agent content from the cloud into local YAML files and push changes back. It also provides the Language Server Protocol (LSP) binary that powers YAML validation — the same engine used by the Copilot Studio web UI. + +What the official extension doesn't do is help you *write* the YAML. That's the gap this toolkit fills. + +For a deeper introduction to Copilot Studio, see the [official documentation](https://learn.microsoft.com/microsoft-copilot-studio/). + +```mermaid +graph TB + subgraph cloud["☁️ Copilot Studio Cloud"] + direction TB + envs["Environments"] + agents_cloud["Agents"] + envs --- agents_cloud + end + + subgraph workspace["📁 Local Workspace"] + direction TB + agent_yml["agent.mcs.yml"] + topics["topics/"] + actions["actions/"] + knowledge["knowledge/"] + lsp["Official Copilot Studio Extension
(LSP binary)"] + end + + subgraph chat["💬 GitHub Copilot Chat"] + direction TB + + subgraph author_grp["@copilot-studio-author"] + direction TB + author_skills["SKILLS

new-topic
add-node
add-action
edit-action
add-knowledge
add-generative-answers
add-other-agents
add-global-variable
edit-agent
edit-triggers
add-adaptive-card
patterns
authoring-tips
validate
lookup-schema
list-kinds
list-topics"] + end + + subgraph manage_grp["@copilot-studio-manage"] + direction TB + manage_skills["SKILLS

manage-agent
clone-agent"] + end + + subgraph test_grp["@copilot-studio-test"] + direction TB + test_skills["SKILLS

detect-mode
chat-directline
chat-sdk
run-tests
validate"] + end + + subgraph troubleshoot_grp["@copilot-studio-troubleshoot"] + direction TB + troubleshoot_skills["SKILLS

known-issues
validate
lookup-schema
list-kinds
list-topics
edit-agent
edit-triggers
run-tests
chat-with-agent"] + end + end + + cloud <-- "push / pull / clone" --> workspace + workspace <-- "reads / writes YAML" --> chat + + style cloud fill:#e8f0fe,stroke:#4285f4,stroke-width:2px,color:#000 + style workspace fill:#fef7e0,stroke:#f9ab00,stroke-width:2px,color:#000 + style chat fill:#e6f4ea,stroke:#34a853,stroke-width:2px,color:#000 + style lsp fill:#fff3e0,stroke:#e65100,stroke-width:1px,color:#000 + style author_grp fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px,color:#000 + style manage_grp fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px,color:#000 + style test_grp fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px,color:#000 + style troubleshoot_grp fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px,color:#000 + style author_skills fill:#e6f4ea,stroke:#a5d6a7,stroke-width:1px,color:#555,font-size:12px + style manage_skills fill:#e6f4ea,stroke:#a5d6a7,stroke-width:1px,color:#555,font-size:12px + style test_skills fill:#e6f4ea,stroke:#a5d6a7,stroke-width:1px,color:#555,font-size:12px + style troubleshoot_skills fill:#e6f4ea,stroke:#a5d6a7,stroke-width:1px,color:#555,font-size:12px +``` + +## The agents and skills + +### How the toolkit evolved + +The skills and agents didn't start life as a VS Code extension. They were born as a [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugin, built by the Copilot Studio Customer Advisory Team (CAT) at Microsoft. The CAT team works directly with customers building production agents and kept running into the same problem: every AI coding assistant they tried would hallucinate Copilot Studio YAML. The `kind:` values looked plausible, the structure was close, but the generated YAML would fail validation because the model was guessing at a schema it had never been trained on. + +The solution was to encode the schema knowledge into skills — structured Markdown instructions that force the LLM to verify every construct against the real schema before writing it. The team bundled these into a Claude Code plugin with session hooks that route requests to specialized sub-agents (Author, Manage, Test, Troubleshoot), each with strict rules about which skills to invoke and when. + +The Claude Code plugin is the most mature surface. It uses platform features like `allowed-tools` to restrict which tools a skill can access, `context: fork` for subprocess isolation, and session hooks that inject a routing prompt at conversation start. The hooks implement an orchestrator pattern: when a user asks to build a topic, the orchestrator delegates to the Author sub-agent, which delegates to the `new-topic` skill, which enforces schema validation. The user never interacts with skills directly — they talk to agents, which talk to skills. + +From Claude Code, the toolkit expanded to [GitHub Copilot CLI](https://docs.github.com/en/copilot). The same plugin format works across both CLI surfaces, so the same agents, skills, and scripts run in GitHub Copilot's terminal-based interface with no changes to the source files. + +The VS Code extension is the third surface — and the one that required real engineering work. Claude Code and GitHub Copilot CLI both understand the plugin format natively (frontmatter fields, session hooks, tool restrictions). VS Code's GitHub Copilot Chat uses a different mechanism: `contributes.chatAgents` and `contributes.chatSkills` registered in `package.json`, with `.agent.md` file extensions and no support for Claude Code-specific frontmatter. Bridging that gap is what the extraction methodology (covered later in this post) is all about. + +The result is a single repository with one set of source files that produces artifacts for all three platforms. Claude Code and GitHub Copilot CLI consume the files directly. The VS Code extension is built from the same sources through a transformation pipeline that strips platform-specific metadata and repackages the content. + +### Four agents for the full lifecycle + +The toolkit provides four agents, each focused on a distinct phase of agent development: + +| Agent | Purpose | +| ------- | --------- | +| `@copilot-studio-author` | Creates and edits topics, actions, knowledge sources, child agents, and global variables | +| `@copilot-studio-manage` | Clones, pushes, pulls, and syncs agent content between local YAML files and the Power Platform cloud | +| `@copilot-studio-test` | Tests published agents with point-tests, batch test suites, or evaluation analysis | +| `@copilot-studio-troubleshoot` | Debugs issues — wrong topic routing, validation errors, unexpected behaviour, hallucinations | + +Each agent is a Markdown file with structured instructions that tell the LLM how to approach a specific class of tasks. The agents enforce strict skill usage — they never write YAML manually. Every operation goes through a skill that has the correct templates, schema validation, and patterns. + +### Thirty-three skills across nine categories + +Skills are the building blocks. Each skill is a `SKILL.md` file in its own directory, containing detailed instructions, schema references, and templates for a specific operation: + +| Category | Skills | +| ---------- | -------- | +| **Authoring** | `new-topic`, `add-action`, `add-node`, `add-knowledge`, `add-adaptive-card`, `add-generative-answers`, `add-global-variable`, `add-other-agents` | +| **Editing** | `edit-agent`, `edit-action`, `edit-triggers` | +| **Validation** | `validate`, `lookup-schema`, `list-kinds`, `list-topics` | +| **Testing** | `chat-with-agent`, `chat-directline`, `chat-sdk`, `directline-chat`, `run-tests-kit`, `test-auth`, `create-eval`, `create-eval-set`, `detect-mode` | +| **Evaluation** | `analyze-evals`, `run-eval` | +| **Management** | `manage-agent`, `clone-agent` | +| **Patterns & Tips** | `patterns` (reusable implementation architectures), `authoring-tips` (practical tips and workarounds) | +| **Troubleshooting** | `known-issues` | +| **Internal** | `int-project-context`, `int-reference` | + +### Where the skills came from + +The Copilot Studio CAT team originally built these as a Claude Code plugin. The skills encode deep domain knowledge about the Copilot Studio YAML schema — which `kind:` values are valid, how triggers work, what frontmatter the platform expects, and how to wire up Power Fx expressions. This knowledge was distilled from months of working with customers building production agents. + +The key design principle: **skills prevent hallucination**. The number one source of errors in AI-generated Copilot Studio YAML is fabricated `kind:` values — action types that look plausible but don't exist in the schema. Every authoring skill mandates a schema lookup before writing any `kind:` value, using a bundled Node.js script (`schema-lookup.bundle.js`) that queries the actual Copilot Studio YAML schema: + +```bash +# List all valid kind values +node scripts/schema-lookup.bundle.js kinds + +# Verify a specific kind exists +node scripts/schema-lookup.bundle.js search SearchAndSummarizeContent + +# Resolve the full definition of a kind +node scripts/schema-lookup.bundle.js resolve AdaptiveDialog +``` + +This means the LLM checks every YAML construct against the real schema before writing it, rather than relying on training data that may be outdated or incomplete. + +## The VS Code extension and Development Bundle + +### Copilot Studio Skills extension + +The [Copilot Studio Skills](https://marketplace.visualstudio.com/items?itemName=coatsy.copilot-studio-skills) extension registers the 4 agents and 24 skills as GitHub Copilot Chat participants. After installing, the agents appear in the `@` mention list in Copilot Chat: + + + +![Copilot Chat @ mention dropdown showing the four Copilot Studio agents](./agent-chooser.png) + +The extension has no runtime code — it's entirely Markdown-driven. The agents and skills are registered through `contributes.chatAgents` and `contributes.chatSkills` in `package.json`, and GitHub Copilot Chat loads the Markdown content as context for the LLM. + +### Copilot Studio Development Bundle + +For the complete experience, the [Copilot Studio Development Bundle](https://marketplace.visualstudio.com/items?itemName=coatsy.copilot-studio-development-bundle) is an extension pack that installs both the Skills extension and the official Copilot Studio extension in one click: + +```bash +code --install-extension coatsy.copilot-studio-development-bundle +``` + +The official extension provides the LSP binary needed for push, pull, clone, and validation operations. The Skills extension provides the AI-powered authoring, testing, and management layer on top. + +## Usage examples + +### Clone an agent + +Start by cloning an agent from the cloud. Ask the Manage agent: + +```text +@copilot-studio-manage Clone an agent from Copilot Studio +``` + +The agent walks you through environment selection, presents a list of agents, and downloads the YAML files. A browser window opens for sign-in — no app registration needed. + +After cloning, your workspace contains: + +```text +my-agent/ + agent.mcs.yml # Agent settings and instructions + settings.mcs.yml # Environment settings + topics/ + greeting.topic.mcs.yml + fallback.topic.mcs.yml + ... + actions/ + knowledge/ + .mcs/ + conn.json # Connection details (tenant, environment, agent) +``` + +### Author a new topic + +Ask the Author agent to create a topic: + +```text +@copilot-studio-author Create a topic called "Product Information" that asks the user +which product category they want and responds with relevant details +``` + +The agent invokes the `new-topic` skill, verifies every `kind:` value against the schema, and generates a YAML file like this: + +```yaml +# Name: Product Information +kind: AdaptiveDialog +beginDialog: + kind: OnRecognizedIntent + id: main + intent: + displayName: Product Information + triggerQueries: + - What products do you offer + - Tell me about your products + - Product information + - Show me product categories + + actions: + - kind: SendActivity + id: sendMessage_a1b2c3 + activity: + text: + - I can help you find information about our products! + + - kind: Question + id: question_d4e5f6 + variable: init:Topic.ProductCategory + prompt: Which product category are you interested in? + entity: StringPrebuiltEntity + alwaysPrompt: true + interruptionPolicy: + allowInterruption: false + + - kind: ConditionGroup + id: conditionGroup_g7h8i9 + conditions: + - id: conditionItem_j1k2l3 + condition: =Topic.ProductCategory = "Hardware" + actions: + - kind: SendActivity + id: sendMessage_m4n5o6 + activity: > + Our hardware lineup includes laptops, desktops, and + peripherals. Visit our catalog for full specifications. + + - id: conditionItem_p7q8r9 + condition: =Topic.ProductCategory = "Software" + actions: + - kind: SendActivity + id: sendMessage_s1t2u3 + activity: > + We offer productivity suites, developer tools, and + cloud services. Check our licensing page for details. + + elseActions: + - kind: SendActivity + id: sendMessage_v4w5x6 + activity: > + I don't have specific details for that category yet. + Let me connect you with someone who can help. +``` + +Every `kind:` value in that file — `AdaptiveDialog`, `OnRecognizedIntent`, `SendActivity`, `Question`, `ConditionGroup` — was verified against the schema before being written. + +### Validate + +Ask the Troubleshoot agent to validate: + +```text +@copilot-studio-troubleshoot Validate all topics in my agent +``` + +The `validate` skill invokes the LSP binary (from the official Copilot Studio extension) to run full diagnostics — YAML structure, Power Fx expressions, schema validation, and cross-file references. It's the same validation engine the Copilot Studio web UI uses. + +### Push, publish, and test + +```text +@copilot-studio-manage Push my changes to Copilot Studio +``` + +After pushing (which creates a draft), publish in the Copilot Studio UI, then test: + +```text +@copilot-studio-test Send "What products do you offer?" to the published agent +``` + +The Test agent detects the authentication mode (DirectLine or integrated auth), establishes a connection, sends the utterance, and returns the agent's response — including the full conversation turn with any adaptive cards or follow-up prompts. + + + +![Testing a published Copilot Studio agent from Copilot Chat using the Test agent](./test-agent-output.png) + +### Add a knowledge source with generative answers + +A common pattern is adding a knowledge source and wiring it up to handle questions the agent doesn't have explicit topics for: + +```text +@copilot-studio-author Add a knowledge source pointing to https://contoso.com/docs +``` + +The `add-knowledge` skill adds the knowledge source to the agent settings. Then, to enable generative answers as a fallback: + +```text +@copilot-studio-author Add generative answers as a fallback for unknown questions +``` + +This creates a topic using the `SearchAndSummarizeContent` kind: + +```yaml +# Name: Knowledge Search +kind: AdaptiveDialog +beginDialog: + kind: OnUnknownIntent + id: main + priority: -1 + actions: + - kind: CreateSearchQuery + id: createSearchQuery_a1b2c3 + userInput: =System.Activity.Text + result: Topic.SearchQuery + + - kind: SearchAndSummarizeContent + id: searchContent_d4e5f6 + variable: Topic.Answer + userInput: =Topic.SearchQuery.SearchQuery + + - kind: ConditionGroup + id: conditionGroup_g7h8i9 + conditions: + - id: conditionItem_j1k2l3 + condition: =!IsBlank(Topic.Answer) + actions: + - kind: EndDialog + id: endDialog_m4n5o6 + clearTopicQueue: true +``` + +When the agent can't match a user's message to any topic, it generates an optimized search query, searches all knowledge sources, and summarizes the results — grounding the response in real data instead of hallucinating. + +## The extraction methodology + +### The design challenge + +The original Claude Code plugin uses platform-specific constructs that don't exist in VS Code: + +- **Frontmatter fields**: `allowed-tools` (restricting which tools a skill can use), `context: fork` (subprocess isolation), `agent` (routing to a parent agent), `argument-hint`, and `user-invocable` +- **Path variables**: `${CLAUDE_SKILL_DIR}` references in 19 of 24 skills, used to locate bundled Node.js scripts +- **File extensions**: Claude Code uses plain `.md` for agents; VS Code requires `.agent.md` + +The challenge was: how do you support two platforms from a single set of source files without creating a maintenance burden? + +### ADR-001: Evaluating the options + +I documented this decision as an [Architecture Decision Record](https://github.com/microsoft/skills-for-copilot-studio/blob/coatsy/vscode-extension/extension/docs/adr/001-artifact-file-strategy.md) (ADR-001) in the repo. Five options were evaluated: + +| Option | Approach | Risk | Why not | +| -------- | ---------- | ------ | --------- | +| A | Single shared files, no transformation | Medium | Untested whether VS Code ignores unknown frontmatter fields | +| B | Build-time copies with full transformation | Medium | Transform script becomes a maintenance burden across 39 files | +| C | Parallel hand-maintained files | High | 40+ files to keep synchronized, guaranteed drift | +| D | Shared files with platform override patches | Low-medium | More engineering for minimal benefit over Option E | +| **E** | **Shared files with frontmatter-only stripping** | **Low** | **Selected** | + +**Option E** won because it provides the best balance of safety, simplicity, and maintainability. The key insight: VS Code Copilot Chat reads skill bodies as LLM context — it doesn't execute bash commands. The `${CLAUDE_SKILL_DIR}` references in skill bodies are informational, not runtime code. Stripping them would lose useful context about what scripts exist, while leaving them is harmless. + +### What the build script does + +The build pipeline is a single Bash script ([`extension/test-local.sh`](https://github.com/microsoft/skills-for-copilot-studio/blob/coatsy/vscode-extension/extension/test-local.sh)) that transforms the source files into a VS Code extension: + +**Step 1: Stage agents**. Copies agent Markdown files from `agents/`, renaming `.md` → `.agent.md` (the suffix VS Code requires for `contributes.chatAgents`). The source files remain as `.md` for Claude Code compatibility. + +**Step 2: Stage skills**. Copies skill directories and strips five Claude Code-specific frontmatter fields from each `SKILL.md`: + +```yaml +# These fields are stripped at build time: +allowed-tools: Bash(node *schema-lookup.bundle.js *), Read, Write, Glob +context: fork +agent: copilot-studio-author +argument-hint: +user-invocable: false +``` + +After stripping, only the portable fields remain (`name`, `description`), plus the full skill body with instructions and examples. + +**Step 3: Resolve paths**. Replaces `${CLAUDE_SKILL_DIR}/../../` references with relative `../../` paths. In the VSIX layout, skills live at `skills//SKILL.md`, so `../../` resolves to the extension root — the same relative structure as the source repo. + +**Step 4: Generate `package.json`**. Dynamically discovers all staged agents and skills and populates the `contributes` section: + +```javascript +// Discovery logic (simplified) +const agents = fs.readdirSync('agents/') + .filter(f => f.endsWith('.agent.md')) + .map(f => ({ + path: './agents/' + f, + name: parseFrontmatter(f).name, + description: parseFrontmatter(f).description + })); + +const skills = fs.readdirSync('skills/') + .filter(d => fs.existsSync(`skills/${d}/SKILL.md`)) + .map(d => ({ + path: `./skills/${d}/SKILL.md`, + name: d, + description: parseFrontmatter(`skills/${d}/SKILL.md`).description + })); + +pkg.contributes = { chatAgents: agents, chatSkills: skills }; +``` + +No manual registration. Add a new agent file or skill directory, and the build picks it up automatically. + +**Step 5: Package**. Runs `vsce package` to produce a `.vsix` file. + + + +### The extension pack + +The Development Bundle is a separate `extension-pack/` directory with its own `package.json` and build script. It's a standard VS Code extension pack that declares dependencies on both extensions: + +```json +{ + "name": "copilot-studio-development-bundle", + "extensionPack": [ + "ms-copilotstudio.vscode-copilotstudio", + "coatsy.copilot-studio-skills" + ] +} +``` + +Both the Skills extension and the Development Bundle are versioned in lockstep and published together. + +## CI/CD and testing + +### Build workflow + +The [build workflow](https://github.com/microsoft/skills-for-copilot-studio/blob/coatsy/vscode-extension/.github/workflows/build-extension.yml) (`build-extension.yml`) runs on every push and pull request that touches agent, skill, script, template, reference, or extension files. It consists of two jobs: + +**Lint job** (runs on Ubuntu and macOS in parallel): + +1. Validates shell script syntax with `bash -n` +2. Runs [ShellCheck](https://www.shellcheck.net/) at warning severity on all scripts + +**Build job** (depends on lint passing): + +1. Sets up Node.js 22 +2. Runs `bash extension/test-local.sh --package-only` with `CODE_CMD=true` (skips the VS Code install step, which isn't available in CI) +3. Verifies a `.vsix` file was produced +4. **Validates VSIX contents**: + - Confirms agents and skills were discovered (counts > 0) + - Verifies no Claude Code-specific fields remain in staged skills (`allowed-tools`, `CLAUDE_SKILL_DIR`, `user-invocable`) + - Checks that all required directories exist (`agents/`, `skills/`, `scripts/`, `templates/`, `reference/`) +5. Builds and verifies the extension pack +6. Uploads both VSIX files as build artifacts (retained for 30 days) + +The validation step is the safety net for the frontmatter stripping. If the transform fails or misses a field, CI catches it. + +### Publish workflow + +The [publish workflow](https://github.com/microsoft/skills-for-copilot-studio/blob/coatsy/vscode-extension/.github/workflows/publish-extension.yml) (`publish-extension.yml`) triggers on GitHub Release creation or manual dispatch: + +1. Resolves the version from the release tag (strips the `v` prefix) +2. Validates the version matches both manifests (`package.template.json` and `extension-pack/package.json`) +3. Builds both VSIX files +4. Publishes to the VS Code Marketplace using a `VSCE_PAT` secret +5. Supports a dry-run mode for testing without publishing + +The version match check prevents accidentally publishing mismatched extensions — the Skills extension and the Development Bundle must always be on the same version. + +### Agent testing + +The toolkit includes three testing approaches for the agents themselves: + +**Point-testing**: The Test agent sends a single utterance to a published agent and returns the response. It auto-detects the agent's authentication mode (DirectLine for agents with no auth or manual auth; Copilot Studio SDK for agents with integrated Entra ID auth) and establishes the appropriate connection. + +**Batch test suites**: Integration with the [Power CAT Copilot Studio Kit](https://github.com/microsoft/Power-CAT-Copilot-Studio-Kit) for running pre-defined test sets with expected responses and pass/fail scoring. + +**Evaluation analysis**: Export evaluation results from the Copilot Studio UI as CSV, then ask the Test agent to analyse failures and propose YAML fixes. + +### LSP-based YAML validation + +The `validate` skill uses the official Copilot Studio LSP binary (shipped with the Copilot Studio extension) to run full diagnostics on agent YAML files. This provides the same validation the web UI uses: YAML structure, Power Fx expression syntax, schema validation, and cross-file reference checking. Validation runs locally — no cloud round-trip required. + + + +![GitHub Actions workflow run showing successful extension build and VSIX validation](./github-actions-build-run.png) + +## Summary + +This project demonstrates a pattern for building cross-platform AI tooling from a single source of truth: + +1. **Author once**: Agents and skills are Markdown files maintained in one location, used by both Claude Code and VS Code +2. **Transform at build time**: A lightweight script strips platform-specific metadata without modifying source files +3. **Discover automatically**: The build dynamically registers agents and skills, so adding new ones requires no manual wiring +4. **Validate in CI**: The pipeline checks that the transformation was complete and the extension contents are correct +5. **Ship atomically**: Both extensions are versioned together and published in a single workflow + +The toolkit is open source and experimental. It's not an officially supported Microsoft product, and the Copilot Studio YAML schema may change without notice. But it's functional, tested, and actively used for building production agents. + +**Try it**: + +- [Install the Copilot Studio Development Bundle](https://marketplace.visualstudio.com/items?itemName=coatsy.copilot-studio-development-bundle) +- [Browse the source on GitHub](https://github.com/coatsy/skills-for-copilot-studio) +- [Read the setup guide](https://github.com/coatsy/skills-for-copilot-studio/blob/main/SETUP_GUIDE.md) +- [File an issue or contribute](https://github.com/coatsy/skills-for-copilot-studio/issues) diff --git a/extension/docs/blog/github-actions-build-run.png b/extension/docs/blog/github-actions-build-run.png new file mode 100644 index 0000000..889e516 Binary files /dev/null and b/extension/docs/blog/github-actions-build-run.png differ diff --git a/extension/docs/blog/hero-screenshot.png b/extension/docs/blog/hero-screenshot.png new file mode 100644 index 0000000..ed64f6f Binary files /dev/null and b/extension/docs/blog/hero-screenshot.png differ diff --git a/extension/docs/blog/test-agent-output.png b/extension/docs/blog/test-agent-output.png new file mode 100644 index 0000000..4266d7c Binary files /dev/null and b/extension/docs/blog/test-agent-output.png differ diff --git a/extension/icon.png b/extension/icon.png new file mode 100644 index 0000000..b1cfed9 Binary files /dev/null and b/extension/icon.png differ diff --git a/extension/setup-devhost.sh b/extension/setup-devhost.sh new file mode 100644 index 0000000..9f17d2e --- /dev/null +++ b/extension/setup-devhost.sh @@ -0,0 +1,225 @@ +#!/usr/bin/env bash +# +# setup-devhost.sh +# Clone, build, and launch a self-hosted VS Code dev instance for extension +# debugging. Idempotent: skips completed steps when re-run. +# +# Usage: +# bash extension/setup-devhost.sh [OPTIONS] +# +# Options: +# --vscode-dir VS Code clone location (default: ./vscode) +# --user-data-dir Isolated user data directory +# (default: .vscode-dev-data) +# --extensions-dir Isolated extensions directory +# (default: .vscode-dev-extensions) +# --skip-launch Clone and build without launching +# --shallow Use --depth 1 for a faster initial clone +# --help Show this help message + +set -euo pipefail + +# ── Defaults ────────────────────────────────────────────────────────────── + +VSCODE_DIR="./vscode" +USER_DATA_DIR=".vscode-dev-data" +EXTENSIONS_DIR=".vscode-dev-extensions" +SKIP_LAUNCH=false +SHALLOW=false +IS_WINDOWS=false + +# ── Functions ───────────────────────────────────────────────────────────── + +usage() { + sed -n '/^# Usage:/,/^$/p' "$0" | sed -E 's/^# ?//' + exit 0 +} + +log() { + local message="$1" + printf "==> %s\n" "$message" +} + +err() { + local message="$1" + printf "ERROR: %s\n" "$message" >&2 + exit 1 +} + +detect_platform() { + case "$(uname -s)" in + MINGW*|MSYS*|CYGWIN*|Windows_NT) + IS_WINDOWS=true + log "Detected Windows (Git Bash / MSYS2). Using .bat CLI scripts." + if ! command -v cl &>/dev/null && [[ -z "${VisualStudioVersion:-}" ]]; then + log "WARNING: C++ build tools not detected. VS Code native modules" + log " require the 'Desktop development with C++' workload from" + log " Visual Studio Build Tools. Install it if the build fails." + log " See extension/docs/LOCAL_DEV_HOST.md for details." + fi + ;; + Darwin*) + log "Detected macOS." + if ! command -v xcodebuild &>/dev/null; then + log "WARNING: Xcode Command Line Tools not detected." + log " Run: xcode-select --install" + fi + ;; + Linux*) + log "Detected Linux." + ;; + *) + log "Unknown platform: $(uname -s). Proceeding with defaults." + ;; + esac +} + +check_prerequisites() { + local missing=() + + if ! command -v git &>/dev/null; then + missing+=("git") + fi + if ! git lfs version &>/dev/null; then + log "WARNING: Git LFS not found. The VS Code repo uses LFS for binary" + log " assets. Clone may fail without it." + log " Install: https://git-lfs.com or 'winget install GitHub.GitLFS'" + fi + if ! command -v node &>/dev/null; then + missing+=("node (Node.js 22+)") + fi + if ! command -v npm &>/dev/null; then + missing+=("npm (bundled with Node.js)") + fi + + if [[ ${#missing[@]} -gt 0 ]]; then + err "Missing prerequisites: ${missing[*]}. \ +See extension/docs/LOCAL_DEV_HOST.md for details." + fi + + local node_major + node_major="$(node --version | sed 's/v\([0-9]*\).*/\1/')" + if (( node_major < 22 )); then + err "Node.js 22+ required (found v${node_major}). \ +See extension/docs/LOCAL_DEV_HOST.md for details." + fi + + detect_platform +} + +clone_vscode() { + if [[ -d "${VSCODE_DIR}/.git" ]]; then + log "VS Code repo already cloned at ${VSCODE_DIR}, skipping clone." + return + fi + + log "Cloning VS Code into ${VSCODE_DIR}..." + log " Skipping LFS binary downloads (not needed for building)." + local clone_args=("https://github.com/microsoft/vscode.git" "${VSCODE_DIR}") + if [[ "${SHALLOW}" == true ]]; then + GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1 "${clone_args[@]}" + else + GIT_LFS_SKIP_SMUDGE=1 git clone "${clone_args[@]}" + fi +} + +install_deps() { + if [[ -d "${VSCODE_DIR}/node_modules" ]]; then + log "Dependencies already installed, skipping npm install." + return + fi + + # Check that the active Node major version matches VS Code's requirement + if [[ -f "${VSCODE_DIR}/.nvmrc" ]]; then + local required_major + required_major="$(sed 's/\..*//' "${VSCODE_DIR}/.nvmrc")" + local active_major + active_major="$(node --version | sed 's/v\([0-9]*\).*/\1/')" + if [[ "${active_major}" != "${required_major}" ]]; then + err "VS Code requires Node.js ${required_major}.x (found v${active_major}). \ +Switch with nvm, fnm, or brew: brew install node@${required_major}" + fi + fi + + log "Installing VS Code dependencies (this may take several minutes)..." + (cd "${VSCODE_DIR}" && npm install) +} + +launch_dev() { + log "Launching self-hosted VS Code dev instance..." + log " User data dir: ${USER_DATA_DIR}" + log " Extensions dir: ${EXTENSIONS_DIR}" + + if [[ "${IS_WINDOWS}" == true ]]; then + log " Using Windows .bat launcher" + (cd "${VSCODE_DIR}" && bash scripts/code.bat \ + --user-data-dir "../${USER_DATA_DIR}" \ + --extensions-dir "../${EXTENSIONS_DIR}") + else + (cd "${VSCODE_DIR}" && bash scripts/code.sh \ + --user-data-dir "../${USER_DATA_DIR}" \ + --extensions-dir "../${EXTENSIONS_DIR}") + fi +} + +# ── Argument Parsing ────────────────────────────────────────────────────── + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --vscode-dir) + VSCODE_DIR="$2" + shift 2 + ;; + --user-data-dir) + USER_DATA_DIR="$2" + shift 2 + ;; + --extensions-dir) + EXTENSIONS_DIR="$2" + shift 2 + ;; + --skip-launch) + SKIP_LAUNCH=true + shift + ;; + --shallow) + SHALLOW=true + shift + ;; + --help) + usage + ;; + *) + err "Unknown option: $1. Use --help for usage." + ;; + esac + done +} + +# ── Main ────────────────────────────────────────────────────────────────── + +main() { + parse_args "$@" + + log "Setting up VS Code dev host for extension debugging" + check_prerequisites + clone_vscode + install_deps + + if [[ "${SKIP_LAUNCH}" == true ]]; then + log "Setup complete. Launch manually from ${VSCODE_DIR}:" + if [[ "${IS_WINDOWS}" == true ]]; then + log " scripts\\code.bat \\" + else + log " bash scripts/code.sh \\" + fi + log " --user-data-dir ../${USER_DATA_DIR} \\" + log " --extensions-dir ../${EXTENSIONS_DIR}" + exit 0 + fi + + launch_dev +} + +main "$@" diff --git a/extension/skills-for-copilot-studio-logo-2.png b/extension/skills-for-copilot-studio-logo-2.png new file mode 100644 index 0000000..b1cfed9 Binary files /dev/null and b/extension/skills-for-copilot-studio-logo-2.png differ diff --git a/extension/skills-for-copilot-studio-logo.png b/extension/skills-for-copilot-studio-logo.png new file mode 100644 index 0000000..2202ac3 Binary files /dev/null and b/extension/skills-for-copilot-studio-logo.png differ diff --git a/extension/templates/package.template.json b/extension/templates/package.template.json new file mode 100644 index 0000000..30e4586 --- /dev/null +++ b/extension/templates/package.template.json @@ -0,0 +1,40 @@ +{ + "name": "copilot-studio-skills", + "displayName": "Copilot Studio Skills", + "extensionKind": [ + "workspace", + "ui" + ], + "version": "1.0.11", + "description": "Copilot Studio YAML authoring, testing, and management skills for GitHub Copilot", + "publisher": "coatsy", + "repository": { + "type": "git", + "url": "https://github.com/coatsy/skills-for-copilot-studio.git" + }, + "bugs": { + "url": "https://github.com/coatsy/skills-for-copilot-studio/issues" + }, + "homepage": "https://github.com/coatsy/skills-for-copilot-studio#readme", + "engines": { + "vscode": "^1.106.1" + }, + "categories": [ + "Chat" + ], + "keywords": [ + "copilot", + "copilot-studio", + "agents", + "skills", + "yaml" + ], + "galleryBanner": { + "color": "#1e1e1e", + "theme": "dark" + }, + "icon": "icon.png", + "contributes": {}, + "author": "Microsoft", + "license": "MIT" +} diff --git a/extension/test-local.sh b/extension/test-local.sh new file mode 100755 index 0000000..012a006 --- /dev/null +++ b/extension/test-local.sh @@ -0,0 +1,444 @@ +#!/usr/bin/env bash +# Build and locally install the Copilot Studio Skills extension for testing. +# Usage: ./extension/test-local.sh [--package-only] +# +# Environment variables: +# CODE_CMD Path to the VS Code CLI (default: auto-detected) +# EXTENSIONS_DIR Custom extensions directory for install (optional) +set -euo pipefail + +# Prerequisite check: node is required for build steps +if ! command -v node &>/dev/null; then + echo "ERROR: node is not installed or not in PATH." + echo "Install Node.js (https://nodejs.org) or ensure your shell can find it." + echo "Tip: If using WSL, configure tasks.json to use Git Bash instead." + exit 1 +fi + +PACKAGE_ONLY=false +[[ "${1:-}" == "--package-only" ]] && PACKAGE_ONLY=true + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +EXT_DIR="$REPO_ROOT/extension" +STAGE_DIR="$EXT_DIR/.staging" + +# On Windows (Git Bash / MSYS2), convert Unix paths to Windows paths for Node.js. +# Node on Windows cannot resolve /d/source/... and produces D:\d\source\... instead. +# Use cygpath -m for forward-slash Windows paths (D:/source/...) which Node handles +# correctly and which survive bash double-quote expansion in node -e strings. +if command -v cygpath &>/dev/null; then + STAGE_DIR_NODE="$(cygpath -m "$STAGE_DIR")" + EXT_DIR_NODE="$(cygpath -m "$EXT_DIR")" +else + STAGE_DIR_NODE="$STAGE_DIR" + EXT_DIR_NODE="$EXT_DIR" +fi + +# Clean and create staging directory +rm -rf "$STAGE_DIR" +mkdir -p "$STAGE_DIR" + +echo "==> Staging artifacts..." + +# Copy agents, renaming .md → .agent.md for VS Code chatAgents compatibility +mkdir -p "$STAGE_DIR/agents" +for f in "$REPO_ROOT/agents"/*.md; do + base="$(basename "$f" .md)" + cp "$f" "$STAGE_DIR/agents/${base}.agent.md" +done + +# Copy skills (will strip frontmatter below) +cp -R "$REPO_ROOT/skills" "$STAGE_DIR/skills" + +# Auto-discover skill references in agent body text and add them to frontmatter. +# Agents reference skills as /copilot-studio: in their body but may +# not declare them in the skills: frontmatter array. VS Code requires skills to +# be listed in frontmatter, so we scan and inject missing ones automatically. +echo "==> Auto-discovering skill references in agents..." +node -e " +const fs = require('fs'); +const path = require('path'); +const agentsDir = path.join('$STAGE_DIR_NODE', 'agents'); +const skillsDir = path.join('$STAGE_DIR_NODE', 'skills'); + +// Build set of valid skill names (directories with SKILL.md) +const validSkills = new Set( + fs.readdirSync(skillsDir).filter(d => + fs.existsSync(path.join(skillsDir, d, 'SKILL.md')) + ) +); + +let totalAdded = 0; + +fs.readdirSync(agentsDir) + .filter(f => f.endsWith('.agent.md')) + .forEach(f => { + const filePath = path.join(agentsDir, f); + const content = fs.readFileSync(filePath, 'utf8'); + + // Split frontmatter from body + const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); + if (!fmMatch) return; + + const fmText = fmMatch[1]; + const body = fmMatch[2]; + + // Parse existing skills from frontmatter + const existingSkills = new Set(); + const skillsLineMatch = fmText.match(/^skills:\s*$/m); + if (skillsLineMatch) { + const afterSkills = fmText.slice(fmText.indexOf(skillsLineMatch[0]) + skillsLineMatch[0].length); + for (const line of afterSkills.split(/\r?\n/)) { + const itemMatch = line.match(/^\s+-\s+(.+)$/); + if (itemMatch) existingSkills.add(itemMatch[1].trim()); + else if (line.match(/^\S/)) break; // next top-level key + } + } + + // Scan body for /copilot-studio: references + const refPattern = /\/copilot-studio:([a-z][a-z0-9-]*)/g; + const referencedSkills = new Set(); + let match; + while ((match = refPattern.exec(body)) !== null) { + const skillName = match[1]; + if (validSkills.has(skillName) && !existingSkills.has(skillName)) { + referencedSkills.add(skillName); + } + } + + if (referencedSkills.size === 0) return; + + // Rebuild frontmatter with merged skills list + const allSkills = [...existingSkills, ...referencedSkills]; + const skillsYaml = 'skills:\n' + allSkills.map(s => ' - ' + s).join('\n'); + + let newFm; + if (skillsLineMatch) { + // Replace existing skills block + const lines = fmText.split(/\r?\n/); + const newLines = []; + let inSkills = false; + for (const line of lines) { + if (line.match(/^skills:\s*$/)) { + inSkills = true; + newLines.push(skillsYaml); + continue; + } + if (inSkills) { + if (line.match(/^\s+-\s+/)) continue; // skip old items + inSkills = false; + } + newLines.push(line); + } + newFm = newLines.join('\n'); + } else { + // No skills key yet — append before closing --- + newFm = fmText + '\n' + skillsYaml; + } + + const newContent = '---\n' + newFm + '\n---\n' + body; + fs.writeFileSync(filePath, newContent); + totalAdded += referencedSkills.size; + console.log(' ' + f + ': added ' + referencedSkills.size + ' skills (' + [...referencedSkills].join(', ') + ')'); + }); + +console.log(' Total: ' + totalAdded + ' skill declarations added'); +" + +# Validate that all /copilot-studio: references resolve to actual skill directories. +# Warns on unresolvable references to catch broken references before packaging. +echo "==> Validating skill references in agents..." +node -e " +const fs = require('fs'); +const path = require('path'); +const agentsDir = path.join('$STAGE_DIR_NODE', 'agents'); +const skillsDir = path.join('$STAGE_DIR_NODE', 'skills'); + +const validSkills = new Set( + fs.readdirSync(skillsDir).filter(d => + fs.existsSync(path.join(skillsDir, d, 'SKILL.md')) + ) +); + +// Agent names are also valid /copilot-studio: references (agent-to-agent invocation) +const agentNames = new Set( + fs.readdirSync(agentsDir) + .filter(f => f.endsWith('.agent.md')) + .map(f => f.replace(/\.agent\.md$/, '')) +); + +let warnings = 0; +fs.readdirSync(agentsDir) + .filter(f => f.endsWith('.agent.md')) + .forEach(f => { + const content = fs.readFileSync(path.join(agentsDir, f), 'utf8'); + const refs = new Set(); + let match; + const pattern = /\/copilot-studio:([a-z][a-z0-9-]*)/g; + while ((match = pattern.exec(content)) !== null) refs.add(match[1]); + for (const ref of refs) { + if (!validSkills.has(ref) && !agentNames.has(ref)) { + console.log(' WARN: ' + f + ' references /copilot-studio:' + ref + ' but no skills/' + ref + '/SKILL.md exists'); + warnings++; + } + } + + // Validate sub-commands: /copilot-studio:skill-name sub-command + const subCmdPattern = /\/copilot-studio:([a-z][a-z0-9-]*)\s+([a-z][a-z0-9-]*)/g; + while ((match = subCmdPattern.exec(content)) !== null) { + const skill = match[1]; + const subCmd = match[2]; + if (!validSkills.has(skill)) continue; + + // Parse argument-hint from skill frontmatter for valid sub-commands + const skillFile = path.join(skillsDir, skill, 'SKILL.md'); + const skillContent = fs.readFileSync(skillFile, 'utf8'); + const fmMatch = skillContent.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (!fmMatch) continue; + const hintMatch = fmMatch[1].match(/^argument-hint:\s*<(.+)>$/m); + if (!hintMatch) continue; + + const validSubCmds = hintMatch[1].split('|').map(s => s.trim()); + if (!validSubCmds.includes(subCmd)) { + console.log(' WARN: ' + f + ' uses /copilot-studio:' + skill + ' ' + subCmd + ' but valid sub-commands are: ' + validSubCmds.join(', ')); + warnings++; + } + } + }); +if (warnings > 0) { + console.log(' ' + warnings + ' unresolvable skill reference(s) found'); +} else { + console.log(' All skill references valid'); +} +" + +# Copy scripts, templates, and reference files +cp -R "$REPO_ROOT/scripts" "$STAGE_DIR/scripts" +cp -R "$REPO_ROOT/templates" "$STAGE_DIR/templates" +cp -R "$REPO_ROOT/reference" "$STAGE_DIR/reference" + +# Copy extension metadata +cp "$EXT_DIR/templates/package.template.json" "$STAGE_DIR/package.json" +cp "$EXT_DIR/LICENSE" "$STAGE_DIR/LICENSE" 2>/dev/null || cp "$REPO_ROOT/LICENSE" "$STAGE_DIR/LICENSE" + +# Generate .vscodeignore to control what goes into the VSIX +cat > "$STAGE_DIR/.vscodeignore" << 'IGNORE' +# Exclude everything by default +** + +# Include extension essentials +!package.json +!README.md +!LICENSE +!icon.png + +# Include agents +!agents/** + +# Include skills +!skills/** + +# Include bundled scripts (not source) +!scripts/*.bundle.js + +# Include templates +!templates/** + +# Include reference files +!reference/** + +# Exclude dev/build artifacts from included directories +**/node_modules/** +**/package-lock.json +**/*.map +IGNORE + +echo "==> Generating package.json with discovered agents and skills..." + +# Populate contributes with discovered agents and skills (including name/description) +node -e " +const fs = require('fs'); +const path = require('path'); +const pkg = JSON.parse(fs.readFileSync('$STAGE_DIR_NODE/package.json', 'utf8')); + +// Extract name and description from YAML frontmatter +function parseFrontmatter(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (!m) return {}; + const fm = {}; + let currentKey = null; + for (const line of m[1].split(/\r?\n/)) { + const kv = line.match(/^([a-z][a-z0-9-]*):\s*(.*)$/i); + if (kv) { + currentKey = kv[1]; + const val = kv[2].replace(/^>/, '').trim(); + if (val) fm[currentKey] = val; + else fm[currentKey] = ''; + } else if (currentKey && line.match(/^\s+\S/)) { + fm[currentKey] = ((fm[currentKey] || '') + ' ' + line.trim()).trim(); + } + } + return fm; +} + +const agents = fs.readdirSync('$STAGE_DIR_NODE/agents') + .filter(f => f.endsWith('.agent.md')) + .map(f => { + const fm = parseFrontmatter(path.join('$STAGE_DIR_NODE/agents', f)); + const entry = { path: './agents/' + f }; + if (fm.name) entry.name = fm.name; + if (fm.description) entry.description = fm.description; + return entry; + }); + +const skills = fs.readdirSync('$STAGE_DIR_NODE/skills') + .filter(d => fs.existsSync(path.join('$STAGE_DIR_NODE/skills', d, 'SKILL.md'))) + .map(d => { + const fm = parseFrontmatter(path.join('$STAGE_DIR_NODE/skills', d, 'SKILL.md')); + const entry = { path: './skills/' + d + '/SKILL.md' }; + entry.name = d; + if (fm.description) entry.description = fm.description; + return entry; + }); + +pkg.contributes = {}; +if (agents.length) pkg.contributes.chatAgents = agents; +if (skills.length) pkg.contributes.chatSkills = skills; + +fs.writeFileSync('$STAGE_DIR_NODE/package.json', JSON.stringify(pkg, null, 2) + '\n'); +console.log(' ' + agents.length + ' agents, ' + skills.length + ' skills'); +" + +# Strip Claude Code-specific frontmatter fields from staged skill files +# (per ADR-001: shared files with frontmatter-only stripping) +echo "==> Stripping Claude Code-specific frontmatter from skills..." +node -e " +const fs = require('fs'); +const path = require('path'); +const stripFields = new Set(['allowed-tools', 'context', 'agent', 'argument-hint', 'user-invocable']); +const skillsDir = '$STAGE_DIR_NODE/skills'; +let stripped = 0; + +fs.readdirSync(skillsDir).forEach(d => { + const skillFile = path.join(skillsDir, d, 'SKILL.md'); + if (!fs.existsSync(skillFile)) return; + + const content = fs.readFileSync(skillFile, 'utf8'); + const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n/); + if (!fmMatch) return; + + const fmLines = fmMatch[1].split('\n'); + const filtered = []; + let skipMultiline = false; + for (const line of fmLines) { + const fieldMatch = line.match(/^([a-z][a-z0-9-]*):/i); + if (fieldMatch && stripFields.has(fieldMatch[1])) { + skipMultiline = true; + stripped++; + continue; + } + if (skipMultiline) { + if (line.match(/^ /) || line.match(/^$/)) continue; + skipMultiline = false; + } + filtered.push(line); + } + + const newFm = '---\n' + filtered.join('\n') + '\n---\n'; + const newContent = newFm + content.slice(fmMatch[0].length); + fs.writeFileSync(skillFile, newContent); +}); +console.log(' Stripped ' + stripped + ' fields across skill files'); +" + +# Replace ${CLAUDE_SKILL_DIR}/../../ with ../../ in staged skill files +# In the VSIX, skills//SKILL.md is two levels deep, so ../../ resolves +# to the extension root — the same layout as the source repo. +echo "==> Resolving script paths in skills..." +node -e " +const fs = require('fs'); +const path = require('path'); +const skillsDir = '$STAGE_DIR_NODE/skills'; +let replaced = 0; + +fs.readdirSync(skillsDir).forEach(d => { + const dir = path.join(skillsDir, d); + if (!fs.statSync(dir).isDirectory()) return; + + fs.readdirSync(dir).filter(f => f.endsWith('.md')).forEach(f => { + const filePath = path.join(dir, f); + const content = fs.readFileSync(filePath, 'utf8'); + // First replace ../../ paths (repo-root-relative), then remaining + // CLAUDE_SKILL_DIR paths (skill-directory-relative) with ./ + let updated = content.replace(/\\$\\{CLAUDE_SKILL_DIR\\}\\/\\.\\.\\/\\.\\.\\//g, '../../'); + updated = updated.replace(/\\$\\{CLAUDE_SKILL_DIR\\}\\//g, './'); + if (updated !== content) { + fs.writeFileSync(filePath, updated); + replaced++; + } + }); +}); +console.log(' Resolved paths in ' + replaced + ' files'); +" + +# Ensure a README exists for vsce, stripping YAML frontmatter +# (VS Code extension view renders frontmatter as visible text) +if [ -f "$EXT_DIR/README.md" ]; then + node -e " + const fs = require('fs'); + const content = fs.readFileSync('$EXT_DIR_NODE/README.md', 'utf8'); + const stripped = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n+/, ''); + fs.writeFileSync('$STAGE_DIR_NODE/README.md', stripped); + " +elif [ ! -f "$STAGE_DIR/README.md" ]; then + echo "# Copilot Studio Skills" > "$STAGE_DIR/README.md" + echo " (created placeholder README.md)" +fi + +# Remove icon field if icon.png doesn't exist +if [ -f "$EXT_DIR/icon.png" ]; then + cp "$EXT_DIR/icon.png" "$STAGE_DIR/icon.png" +else + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('$STAGE_DIR_NODE/package.json', 'utf8')); + delete pkg.icon; + fs.writeFileSync('$STAGE_DIR_NODE/package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + echo " (removed icon field — no icon.png found)" +fi + +echo "==> Packaging extension..." +cd "$STAGE_DIR" +npx --yes @vscode/vsce package --no-dependencies --allow-missing-repository 2>&1 + +VSIX=$(ls -t *.vsix 2>/dev/null | head -1) +if [ -z "$VSIX" ]; then + echo "ERROR: No .vsix file produced" + exit 1 +fi + +# Move VSIX to extension directory +mv "$STAGE_DIR/$VSIX" "$EXT_DIR/$VSIX" + +if [ "$PACKAGE_ONLY" = true ]; then + echo "" + echo "==> Done! VSIX built at extension/$VSIX" + exit 0 +fi + +echo "" +echo "==> Installing $VSIX..." +CODE_CMD="${CODE_CMD:-$(command -v code 2>/dev/null || echo "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code")}" + +INSTALL_ARGS=(--install-extension "$EXT_DIR/$VSIX") +if [ -n "${EXTENSIONS_DIR:-}" ]; then + INSTALL_ARGS=(--extensions-dir "$EXTENSIONS_DIR" "${INSTALL_ARGS[@]}") +fi + +"$CODE_CMD" "${INSTALL_ARGS[@]}" + +echo "" +echo "==> Done! Reload VS Code to activate the extension." +echo " To uninstall: $CODE_CMD --uninstall-extension coatsy.copilot-studio-skills" diff --git a/templates/actions/connector-action.mcs.yml b/templates/actions/connector-action.mcs.yml index 595943c..8524195 100644 --- a/templates/actions/connector-action.mcs.yml +++ b/templates/actions/connector-action.mcs.yml @@ -1,12 +1,11 @@ -# Name: -# Description: +mcs.metadata: + componentName: + description: "Description: " kind: TaskDialog inputs: - kind: ManualTaskInput propertyName: value: - # NOTE: For $-prefixed OData params (SharePoint), use: propertyName: "'$filter'" - # The inner single quotes are required by the runtime. See edit-action/sharepoint-actions.md. - kind: AutomaticTaskInput propertyName: @@ -16,7 +15,6 @@ inputs: modelDisplayName: modelDescription: - outputs: - propertyName: Response @@ -25,6 +23,7 @@ action: connectionReference: connectionProperties: mode: Invoker + operationId: -outputMode: All +outputMode: All \ No newline at end of file diff --git a/templates/actions/mcp-action.mcs.yml b/templates/actions/mcp-action.mcs.yml index 6348755..92998cd 100644 --- a/templates/actions/mcp-action.mcs.yml +++ b/templates/actions/mcp-action.mcs.yml @@ -1,5 +1,6 @@ -# Name: -# Description: +mcs.metadata: + componentName: + description: "Description: " kind: TaskDialog modelDisplayName: modelDescription: @@ -8,6 +9,7 @@ action: connectionReference: connectionProperties: mode: Invoker + operationDetails: kind: ModelContextProtocolMetadata - operationId: + operationId: \ No newline at end of file diff --git a/upstream-version.json b/upstream-version.json new file mode 100644 index 0000000..af0580b --- /dev/null +++ b/upstream-version.json @@ -0,0 +1,6 @@ +{ + "upstream_repo": "microsoft/skills-for-copilot-studio", + "upstream_version": "v1.0.11", + "last_checked": "2026-05-17", + "notes": "Tracked by check-upstream-release workflow. Updated when a new upstream release is detected and processed." +}