diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..e394c7e --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,83 @@ +# Runs automated quality checks on pull requests before merging. +# Validates code quality by running linting, compilation, tests, and agent file checks. +# Serves as a quality gate to prevent broken code or oversized agent files from being merged. +# +# Path filtering ensures workflow only runs when relevant files change. +# Uses xvfb for headless VS Code extension testing. +name: PR Checks + +on: + pull_request: + branches: + - main + paths: + - 'src/**' + - 'agents/**' + - 'skills/**' + - 'cli/**' + - 'scripts/**' + - '.github/workflows/pr-checks.yml' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Lint code + run: npm run lint + + - name: Compile extension + run: npm run compile + + - name: Run extension unit tests + run: | + # Install xvfb for headless VS Code testing + # xvfb provides a virtual display server, required for VS Code's Electron environment + sudo apt-get update + sudo apt-get install -y xvfb + + # Run tests with virtual display + # xvfb-run -a automatically finds an available display number + xvfb-run -a npm test + env: + # Prevent VS Code from showing UI during tests + DISPLAY: ':99.0' + + - name: Install CLI dependencies + run: cd cli && npm ci + + - name: Run CLI tests + run: | + cd cli + # Check if test files exist before running + if ls lib/*.test.js 1> /dev/null 2>&1; then + npm test + else + echo "No CLI test files found (lib/*.test.js) - skipping" + fi + + - name: Lint agent files + run: npm run lint:agent:all + + - name: PR checks summary + if: success() + run: | + echo "✅ All PR checks passed!" + echo "- Code linting: PASSED" + echo "- Compilation: PASSED" + echo "- Extension tests: PASSED" + echo "- CLI tests: PASSED" + echo "- Agent linting: PASSED" diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml new file mode 100644 index 0000000..92352e0 --- /dev/null +++ b/.github/workflows/publish-cli.yml @@ -0,0 +1,129 @@ +# Publishes the CLI package to npm when CLI version tags are pushed. +# Triggers on tags matching cli-v* pattern (e.g., cli-v1.0.0, cli-v0.2.0-beta). +# Builds, tests, publishes to npm with provenance, and creates GitHub Release. +# +# Pre-release convention: versions with -alpha, -beta, or -rc suffix are pre-releases +# and are published to npm with the 'beta' tag instead of 'latest'. +# +# AUTHENTICATION: +# This workflow uses NPM_TOKEN secret for npm authentication. +# To set up: +# 1. Generate an npm access token at https://www.npmjs.com/settings/tokens +# 2. Add as repository secret: Settings → Secrets → Actions → NPM_TOKEN +# +# For enhanced security with provenance, you can also configure npm OIDC: +# 1. In cli/package.json, add: "publishConfig": { "provenance": true } +# 2. On npmjs.com, link the package to the GitHub repository +# 3. The id-token permission below enables OIDC attestation +# +# PREREQUISITES (first-time setup): +# 1. Create NPM_TOKEN secret in repository settings +# 2. First publish may need to be manual: cd cli && npm publish --access public +name: Publish CLI + +on: + push: + tags: + - 'cli-v*' + +permissions: + id-token: write # Required for OIDC trusted publishing to npm + contents: write # Required to create GitHub releases + +jobs: + publish: + runs-on: ubuntu-latest + defaults: + run: + working-directory: cli + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + # Using Node 20 for consistency with extension workflow + # PAW uses Node 24, but 20 is sufficient and more stable + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Extract version from tag + id: version + run: | + TAG_NAME="${{ github.ref_name }}" + VERSION="${TAG_NAME#cli-v}" # Remove 'cli-v' prefix + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "Extracted version: ${VERSION}" + + - name: Determine pre-release status + id: prerelease + run: | + VERSION="${{ steps.version.outputs.version }}" + # Check for pre-release suffixes: -alpha, -beta, -rc + # Examples: 1.0.0-beta, 0.2.0-alpha.1, 1.0.0-rc.1 + if [[ "$VERSION" =~ -(alpha|beta|rc) ]]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "npm_tag=beta" >> $GITHUB_OUTPUT + echo "This is a pre-release version" + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "npm_tag=latest" >> $GITHUB_OUTPUT + echo "This is a stable release version" + fi + + - name: Set version from tag + run: npm version ${{ steps.version.outputs.version }} --no-git-tag-version + + - name: Install dependencies + run: npm ci + + - name: Build distribution + run: npm run build + + - name: Run tests + run: npm test + + - name: Publish to npm + run: npm publish --access public --tag ${{ steps.prerelease.outputs.npm_tag }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Check if release exists + id: check_release + working-directory: ${{ github.workspace }} + run: | + RELEASE_EXISTS=$(gh release view "${{ github.ref_name }}" --json id 2>/dev/null || echo "") + if [ -n "$RELEASE_EXISTS" ]; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub Release + if: steps.check_release.outputs.exists == 'false' + uses: softprops/action-gh-release@v1 + with: + name: CLI ${{ steps.version.outputs.version }} + tag_name: ${{ github.ref_name }} + body: | + Published to npm: `@erdem-tuna/markdown-commenter@${{ steps.version.outputs.version }}` + + ## Installation + + ```bash + npx @erdem-tuna/markdown-commenter install copilot + ``` + + Or install globally: + ```bash + npm install -g @erdem-tuna/markdown-commenter + markdown-commenter install copilot + ``` + + Release notes will be added by the release preparation process. + prerelease: ${{ steps.prerelease.outputs.is_prerelease }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6b2589f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,156 @@ +# Automatically builds and releases the VS Code extension when version tags are pushed. +# Triggers on tags matching v* pattern (e.g., v0.2.0, v1.0.0). +# Builds extension, packages VSIX, creates GitHub Release, and publishes to VS Code Marketplace. +# +# Pre-release convention: odd minor versions (0.1.x, 0.3.x) are pre-releases, +# even minor versions (0.2.x, 0.4.x) are stable releases. +# +# SECRETS REQUIRED: +# - VSCE_PAT: Personal Access Token for VS Code Marketplace publishing +# To create: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token +# Required scopes: Marketplace (Manage) +# Add to repository: Settings → Secrets and variables → Actions → New repository secret +# +# The workflow will create a GitHub Release even if Marketplace upload fails, +# ensuring users can always download the VSIX manually. +name: Release VSIX + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + + permissions: + contents: write # Required to create releases and upload assets + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'package-lock.json' + + - name: Install dependencies + run: npm ci + + - name: Compile extension + run: npm run compile + + - name: Extract version from tag + id: version + run: | + # Extract semantic version from tag (e.g., v0.2.0 -> 0.2.0) + # This version becomes the source of truth for the release + TAG_NAME="${{ github.ref_name }}" + VERSION="${TAG_NAME#v}" # Remove 'v' prefix using bash parameter expansion + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "Extracted version: ${VERSION}" + + - name: Update package.json version to match tag + run: | + # Set package.json version to match the git tag + # This eliminates the need for manual version updates before tagging + # Local development stays on "0.0.1-dev" so every activation reinstalls agents + # The release workflow overwrites that dev suffix with the semver tag + TAG_VERSION="${{ steps.version.outputs.version }}" + npm version ${TAG_VERSION} --no-git-tag-version --allow-same-version + echo "Updated package.json to version: ${TAG_VERSION}" + + - name: Determine pre-release status + id: prerelease + run: | + # VS Code extension convention: odd minor versions (0.1.x, 0.3.x) are pre-releases, + # even minor versions (0.2.x, 0.4.x) are stable releases + # This follows the VS Code pre-release extension best practices + VERSION="${{ steps.version.outputs.version }}" + # Extract minor version (second number in semver, e.g., 0.2.0 -> 2) + MINOR=$(echo $VERSION | cut -d. -f2) + # Check if minor version is odd (pre-release) or even (stable) + if [ $((MINOR % 2)) -eq 1 ]; then + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "This is a pre-release version (minor=$MINOR is odd)" + else + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "This is a stable release version (minor=$MINOR is even)" + fi + + - name: Package VSIX + run: npm run package + + - name: Verify VSIX created + id: vsix + run: | + # Verify the VSIX file was created with the expected filename format + # The package name comes from package.json: markdown-commenter-.vsix + VSIX_FILE="markdown-commenter-${{ steps.version.outputs.version }}.vsix" + if [ ! -f "$VSIX_FILE" ]; then + echo "Error: Expected VSIX file not found: $VSIX_FILE" + exit 1 + fi + # Export both the full path (for release upload) and filename (for display) + echo "vsix_path=${VSIX_FILE}" >> $GITHUB_OUTPUT + echo "vsix_name=${VSIX_FILE}" >> $GITHUB_OUTPUT + echo "VSIX created successfully: $VSIX_FILE" + + - name: Check if release exists + id: check_release + run: | + RELEASE_EXISTS=$(gh release view "${{ github.ref_name }}" --json id 2>/dev/null || echo "") + if [ -n "$RELEASE_EXISTS" ]; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub Release + if: steps.check_release.outputs.exists == 'false' + uses: softprops/action-gh-release@v1 + with: + name: ${{ github.ref_name }} + tag_name: ${{ github.ref_name }} + body: | + Release notes will be added by the release preparation process. + + ## Installation + + Download the `.vsix` file and install via: + ```bash + code --install-extension ${{ steps.vsix.outputs.vsix_name }} + ``` + + Or install from VS Code Marketplace (if published successfully). + files: ${{ steps.vsix.outputs.vsix_path }} + prerelease: ${{ steps.prerelease.outputs.is_prerelease }} + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish to VS Code Marketplace + id: marketplace + # continue-on-error ensures GitHub Release is created even if Marketplace fails + # This provides resilience: users can always download VSIX from GitHub + continue-on-error: true + run: | + echo "Publishing to VS Code Marketplace..." + npx vsce publish --pat ${{ secrets.VSCE_PAT }} + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + - name: Marketplace publish status + if: always() + run: | + if [ "${{ steps.marketplace.outcome }}" == "failure" ]; then + echo "⚠️ VS Code Marketplace publish failed" + echo "GitHub Release was still created with VSIX attached" + echo "You can manually publish later with: npx vsce publish" + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..14ff835 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,114 @@ +# Contributing to Markdown Commenter + +Thank you for your interest in contributing! This guide covers development setup, workflows, and maintainer tasks. + +## Development Setup + +### Prerequisites + +- Node.js 18+ +- npm 9+ +- VS Code (for extension development) + +### Build from Source + +```bash +git clone https://github.com/erdemtuna/markdown-commenter +cd markdown-commenter +npm install +npm run compile +``` + +### Run Tests + +```bash +npm test # Extension tests (requires VS Code) +cd cli && npm test # CLI tests +``` + +### Lint + +```bash +npm run lint # TypeScript/ESLint +npm run lint:agent:all # Agent/skill token limits +``` + +### Package Extension + +```bash +npm run package # Creates .vsix file +``` + +## Pull Request Workflow + +1. Fork the repository +2. Create a feature branch from `main` +3. Make your changes with tests +4. Ensure all checks pass locally: + ```bash + npm run lint && npm run compile && npm test + ``` +5. Submit a pull request + +PRs trigger automated checks: lint, compile, test, and agent token linting. + +## Release Process + +Releases are automated via GitHub Actions using git tags: + +| Release Type | Tag Pattern | Example | +|--------------|-------------|---------| +| Extension | `v*` | `git tag v1.2.0 && git push origin v1.2.0` | +| CLI | `cli-v*` | `git tag cli-v1.2.0 && git push origin cli-v1.2.0` | + +### Version Conventions + +- **Extension**: Odd minor versions (e.g., `v1.1.0`) are pre-releases +- **CLI**: Use semver suffixes for pre-releases (e.g., `cli-v1.0.0-beta.1`) + +--- + +## Maintainer Guide + +### Repository Secrets + +The following GitHub Actions secrets must be configured for CI/CD: + +| Secret | Required For | How to Get | +|--------|--------------|------------| +| `VSCE_PAT` | Extension releases | [Azure DevOps](https://dev.azure.com) → User Settings → Personal Access Tokens → New Token with **Marketplace (Publish)** scope | +| `NPM_TOKEN` | CLI publishing | [npmjs.com](https://www.npmjs.com/settings/~/tokens) → Access Tokens → Generate New Token (Automation) | + +**Configure at:** Repository → Settings → Secrets and variables → Actions → New repository secret + +### First-Time CLI Publish + +The first npm publish must be done manually to establish the package: + +```bash +cd cli +npm login +npm publish --access public +``` + +Subsequent publishes use the `NPM_TOKEN` secret automatically. + +### Token Limits + +Agent and skill files have token limits enforced by CI: + +| File Type | Warning | Error | +|-----------|---------|-------| +| Agents (`agents/*.agent.md`) | 5,000 | 7,000 | +| Skills (`skills/*/SKILL.md`) | 8,000 | 12,000 | + +Check locally with: +```bash +npm run lint:agent:all +``` + +### Workflow Files + +- `.github/workflows/pr-checks.yml` — PR quality gates +- `.github/workflows/release.yml` — Extension release (v* tags) +- `.github/workflows/publish-cli.yml` — CLI npm publish (cli-v* tags) diff --git a/README.md b/README.md index 03b825e..ef686f7 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,14 @@ npm run compile ### Run tests ```bash -npm test +npm test # Extension tests +cd cli && npm test # CLI tests +``` + +### Lint agent files + +```bash +npm run lint:agent:all # Lint agents and skills for token limits ``` ### Package extension @@ -144,6 +151,15 @@ npm test npm run package ``` +### Release process + +Releases are automated via GitHub Actions: + +- **Extension**: Push a `v*` tag (e.g., `git tag v1.0.0 && git push origin v1.0.0`) +- **CLI**: Push a `cli-v*` tag (e.g., `git tag cli-v1.0.0 && git push origin cli-v1.0.0`) + +See [CONTRIBUTING.md](CONTRIBUTING.md) for maintainer setup (secrets, first-time publish, etc.). + ## Contributing Contributions welcome! Please: diff --git a/WorkShaping-GHActions.md b/WorkShaping-GHActions.md new file mode 100644 index 0000000..d51ec5a --- /dev/null +++ b/WorkShaping-GHActions.md @@ -0,0 +1,226 @@ +# Work Shaping: GitHub Actions CI/CD for markdown-commenter + +## Problem Statement + +**Who benefits**: Maintainers and contributors of the markdown-commenter project. + +**What problem is solved**: Currently, markdown-commenter has no automated build, test, or release pipeline. Publishing the VS Code extension and Copilot CLI requires manual steps that are error-prone and time-consuming. This work establishes GitHub Actions workflows modeled after the PAW project to automate quality gates and releases. + +## Work Breakdown + +### Core Functionality + +1. **PR Checks Workflow** (`pr-checks.yml`) + - Triggered on PRs to `main` (and feature branches if applicable) + - Path filtering: `src/**`, `agents/**`, `skills/**`, `cli/**`, `.github/workflows/**` + - Steps: + - Checkout, setup Node.js 20, cache npm dependencies + - `npm run lint` — TypeScript linting + - `npm run compile` — build extension + - `xvfb-run -a npm test` — VS Code extension tests (headless) + - `cd cli && npm test` — CLI tests + - `npm run lint:agent:all` — agent/skill token linting + +2. **Extension Release Workflow** (`release.yml`) + - Triggered on `v*` tags (e.g., `v1.0.0`, `v0.3.0`) + - Steps: + - Extract version from tag, update package.json + - Determine pre-release status (odd minor = pre-release) + - Compile and package VSIX + - Verify VSIX file exists + - Check if GitHub Release already exists (skip if yes) + - Create GitHub Release with VSIX attached + - Publish to VS Code Marketplace (continue on failure, log warning) + - Secrets required: `VSCE_PAT` (documented in workflow comments) + +3. **CLI Publish Workflow** (`publish-cli.yml`) + - Triggered on `cli-v*` tags (e.g., `cli-v1.0.0`) + - Working directory: `cli/` + - Steps: + - Extract version from tag + - Determine pre-release status (semver suffixes: -alpha, -beta, -rc) + - Set package.json version from tag + - Install dependencies, build, run tests + - Verify dist/ contents are complete + - Publish to npm with OIDC trusted publishing (provenance) + - Check if GitHub Release exists (skip if yes) + - Create GitHub Release + - Authentication: npm OIDC (no token secret needed, requires npm package setup) + +### Supporting Infrastructure + +4. **Agent/Skill Linting Scripts** + - Copy and adapt from PAW: + - `scripts/lint-prompting.sh` — token size linter + - `scripts/count-tokens.js` — tiktoken-based counter + - Adapt for markdown-commenter paths and structure + - Add `@dqbd/tiktoken` as devDependency + - Add `lint:agent`, `lint:agent:all`, `lint:skills` npm scripts + +5. **Documentation** + - Inline comments in workflows explaining: + - How to obtain and set `VSCE_PAT` + - npm OIDC setup requirements + - Branch protection recommendations + - Pre-release versioning convention + +## Edge Cases & Expected Handling + +| Scenario | Handling | +|----------|----------| +| VS Code Marketplace upload fails | Continue workflow, log warning; GitHub Release still created with VSIX | +| VSIX packaging fails | Fail workflow entirely | +| CLI build produces incomplete dist/ | Fail workflow entirely | +| Missing changelog/release notes | Create release with placeholder text (user fills in later) | +| Same tag pushed twice | Skip release creation (idempotent) | +| package.json version ≠ tag | Tag is source of truth; workflow sets package.json version from tag | +| npm publish auth failure | Fail workflow (OIDC misconfiguration needs fixing) | +| Tag pushed from non-main branch | Use branch protection rules to prevent; document as requirement | + +## Rough Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ GitHub Repository │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ PR to main ─────────────────┐ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ PR Checks │ │ +│ │ (pr-checks.yml)│ │ +│ └────────┬────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────┐ │ +│ │ lint → compile → test │ │ +│ │ extension + CLI + agents │ │ +│ └──────────────────────────┘ │ +│ │ +│ Push tag v* ────────────────┐ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ Extension │ │ +│ │ Release │ │ +│ │ (release.yml) │ │ +│ └────────┬────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────┐ │ +│ │ Build VSIX │ │ +│ │ │ │ │ +│ │ ├─► GitHub │ │ +│ │ │ Release │ │ +│ │ │ │ │ +│ │ └─► VS Code │ │ +│ │ Marketplace │ │ +│ └──────────────────────────┘ │ +│ │ +│ Push tag cli-v* ────────────┐ │ +│ ▼ │ +│ ┌─────────────────┐ │ +│ │ CLI Publish │ │ +│ │(publish-cli.yml)│ │ +│ └────────┬────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────────┐ │ +│ │ Build CLI │ │ +│ │ │ │ │ +│ │ ├─► npm registry │ │ +│ │ │ │ │ +│ │ └─► GitHub │ │ +│ │ Release │ │ +│ └──────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Critical Analysis + +### Value Assessment + +**High value**: This automation eliminates manual release steps, ensures consistent quality gates, and aligns markdown-commenter with PAW's mature CI/CD practices. The one-time setup cost pays off quickly with every release. + +### Build vs. Modify Tradeoffs + +**Approach**: Model after PAW's existing workflows rather than starting from scratch. + +**Why copy/adapt vs. reusable templates**: +- These two projects have similar but not identical structures +- Directly importing workflows would create tight coupling +- Copy-and-adapt allows project-specific customization while sharing patterns +- Future: Consider extracting reusable composite actions if more projects adopt this pattern + +### Risks + +1. **VSCE_PAT expiration**: Personal Access Tokens expire; document renewal process +2. **npm OIDC setup**: Requires npm package configuration; document prerequisites +3. **tiktoken dependency**: External dependency for token counting; pin version +4. **Branch protection overhead**: May slow down emergency hotfixes; document bypass procedure + +## Codebase Fit + +### Similar Features in PAW + +- `/.github/workflows/pr-checks.yml` — quality gate workflow +- `/.github/workflows/release.yml` — VSIX release workflow +- `/.github/workflows/publish-cli.yml` — npm publish workflow +- `/scripts/lint-prompting.sh` — agent/skill linting +- `/scripts/count-tokens.js` — token counting utility + +### Reuse Opportunities + +| PAW File | Reuse Strategy | +|----------|----------------| +| `pr-checks.yml` | Copy, adapt paths and job names | +| `release.yml` | Copy, add Marketplace publish step, adapt package name | +| `publish-cli.yml` | Copy, adapt package name and scope | +| `lint-prompting.sh` | Copy as-is (generic enough) | +| `count-tokens.js` | Copy, remove PAW-specific template expansion if not needed | + +## Risk Assessment + +### Potential Negative Impacts + +1. **Failed releases leave orphaned tags**: Document tag cleanup procedure +2. **Marketplace publish failures could delay user access**: Mitigated by always creating GitHub Release first +3. **Token linting may block legitimate large agents**: Thresholds are warnings first, errors only at extreme sizes + +### Gotchas + +- **npm OIDC requires package.json `publishConfig`**: Document in workflow +- **VS Code extension tests need `xvfb`**: Already handled in PAW's PR checks +- **CLI working directory**: Workflows must use `working-directory: cli` +- **Package names differ**: markdown-commenter vs paw-workflow in VSIX filenames + +## Open Questions for Downstream Stages + +1. **Marketplace publisher ID**: Is `erdem-tuna` the correct publisher for VS Code Marketplace? +2. **npm package scope**: Confirm `@erdem-tuna/markdown-commenter` is the intended npm scope +3. **Token thresholds**: Use PAW's defaults (5K warn, 7K error for agents; 8K/12K for skills)? +4. **Should CLI tests run as part of PR checks?**: Currently planned as yes +5. **MkDocs workflow**: PAW has `docs.yml` for documentation — needed for markdown-commenter? + +## Session Notes + +### Key Decisions + +- **Tag-triggered releases**: Matches PAW pattern, allows deliberate releases +- **Separate tag patterns**: `v*` for extension, `cli-v*` for CLI (independent versioning) +- **Even/odd minor versioning**: Aligns with VS Code extension pre-release convention +- **OIDC over access tokens**: More secure npm authentication +- **Skip Marketplace on failure**: Resilience over strict consistency; GitHub Release is primary + +### Rejected Alternatives + +- **Reusable workflow templates**: Rejected in favor of copy/adapt for project independence +- **Auto-release on PR merge**: Rejected; tag-triggered gives more control +- **Single tag for both artifacts**: Rejected; independent versioning is more flexible +- **Access token for npm**: Rejected; OIDC is best practice + +### Surprising Discoveries + +- PAW already has comprehensive workflows that can serve as templates +- markdown-commenter's CLI structure closely mirrors PAW's CLI structure +- Agent linting infrastructure (tiktoken) requires a devDependency addition diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 0000000..aaa2657 Binary files /dev/null and b/images/icon.png differ diff --git a/images/icon.svg b/images/icon.svg deleted file mode 100644 index 2468e52..0000000 --- a/images/icon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/package-lock.json b/package-lock.json index 74987f9..b276964 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "version": "0.0.1-dev", "license": "MIT", "dependencies": { - "@fluentui/react-icons": "^2.0.319", "@fluentui/tokens": "^1.0.0-alpha.23", "@fluentui/web-components": "^3.0.0-beta.133" }, "devDependencies": { + "@dqbd/tiktoken": "^1.0.0", "@types/glob": "^8.1.0", "@types/mocha": "^10.0.6", "@types/node": "^20.11.0", @@ -199,19 +199,11 @@ "node": ">=16" } }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "node_modules/@dqbd/tiktoken": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@dqbd/tiktoken/-/tiktoken-1.0.22.tgz", + "integrity": "sha512-RYhO8xeHkMNX5Ixqf4M1Ve3siCYJY/dI0yLnlX4M4oIEDOvjMIQ+E+3OUpAaZcWTaMtQJzGcDAghYfllpx3i/w==", + "dev": true, "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { @@ -301,19 +293,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fluentui/react-icons": { - "version": "2.0.319", - "resolved": "https://registry.npmjs.org/@fluentui/react-icons/-/react-icons-2.0.319.tgz", - "integrity": "sha512-4yN0ovAxlOXL5jM3ULuYu6pAwvMWm1MjojDXn5tVKDkXQwlLpXCUFwf5Vgzp5jU6jJXrENj4v64MN6w7FJjotw==", - "license": "MIT", - "dependencies": { - "@griffel/react": "^1.0.0", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "react": ">=16.8.0 <20.0.0" - } - }, "node_modules/@fluentui/tokens": { "version": "1.0.0-alpha.23", "resolved": "https://registry.npmjs.org/@fluentui/tokens/-/tokens-1.0.0-alpha.23.tgz", @@ -350,42 +329,6 @@ "@swc/helpers": "^0.5.1" } }, - "node_modules/@griffel/core": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@griffel/core/-/core-1.19.2.tgz", - "integrity": "sha512-WkB/QQkjy9dE4vrNYGhQvRRUHFkYVOuaznVOMNTDT4pS9aTJ9XPrMTXXlkpcwaf0D3vNKoerj4zAwnU2lBzbOg==", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.0", - "@griffel/style-types": "^1.3.0", - "csstype": "^3.1.3", - "rtl-css-js": "^1.16.1", - "stylis": "^4.2.0", - "tslib": "^2.1.0" - } - }, - "node_modules/@griffel/react": { - "version": "1.5.32", - "resolved": "https://registry.npmjs.org/@griffel/react/-/react-1.5.32.tgz", - "integrity": "sha512-jN3SmSwAUcWFUQuQ9jlhqZ5ELtKY21foaUR0q1mJtiAeSErVgjkpKJyMLRYpvaFGWrDql0Uz23nXUogXbsS2wQ==", - "license": "MIT", - "dependencies": { - "@griffel/core": "^1.19.2", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "react": ">=16.8.0 <20.0.0" - } - }, - "node_modules/@griffel/style-types": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@griffel/style-types/-/style-types-1.3.0.tgz", - "integrity": "sha512-bHwD3sUE84Xwv4dH011gOKe1jul77M1S6ZFN9Tnq8pvZ48UMdY//vtES6fv7GRS5wXYT4iqxQPBluAiYAfkpmw==", - "license": "MIT", - "dependencies": { - "csstype": "^3.1.3" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1645,12 +1588,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4301,16 +4238,6 @@ "node": ">=0.10.0" } }, - "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -4425,15 +4352,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rtl-css-js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", - "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2" - } - }, "node_modules/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", @@ -4813,12 +4731,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylis": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", - "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT" - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index c75e8ff..3439449 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "version": "0.0.1-dev", "publisher": "erdem-tuna", "license": "MIT", + "icon": "images/icon.png", "repository": { "type": "git", "url": "https://github.com/erdem-tuna/markdown-commenter" @@ -119,9 +120,13 @@ "cli:build": "cd cli && npm run build", "cli:link": "cd cli && npm link", "cli:install-local": "npm run cli:build && node cli/bin/cli.js install copilot", - "cli:reinstall": "node cli/bin/cli.js uninstall; rm -rf ~/.paw/markdown-commenter; npm run cli:install-local" + "cli:reinstall": "node cli/bin/cli.js uninstall; rm -rf ~/.paw/markdown-commenter; npm run cli:install-local", + "lint:agent": "./scripts/lint-prompting.sh", + "lint:agent:all": "./scripts/lint-prompting.sh --all", + "lint:skills": "./scripts/lint-prompting.sh --skills" }, "devDependencies": { + "@dqbd/tiktoken": "^1.0.0", "@types/glob": "^8.1.0", "@types/mocha": "^10.0.6", "@types/node": "^20.11.0", diff --git a/scripts/count-tokens.js b/scripts/count-tokens.js new file mode 100644 index 0000000..e88aa8c --- /dev/null +++ b/scripts/count-tokens.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node +/** + * Count tokens in a file using tiktoken + * Usage: node scripts/count-tokens.js [model] + * + * This script counts tokens in markdown files (agents, skills) to ensure + * they don't exceed size limits that would degrade LLM performance. + */ + +const fs = require('fs'); +const path = require('path'); +const { encoding_for_model } = require('@dqbd/tiktoken'); + +// Get command line arguments +const args = process.argv.slice(2); + +if (args.length === 0) { + console.error('Usage: count-tokens.js [model]'); + process.exit(1); +} + +const filePath = args[0]; +const model = args[1] || 'gpt-4o-mini'; + +// Check if file exists +if (!fs.existsSync(filePath)) { + console.error(`Error: File not found: ${filePath}`); + process.exit(1); +} + +try { + // Read file content + const absolutePath = path.resolve(filePath); + const content = fs.readFileSync(absolutePath, 'utf-8'); + + // Get encoding for the model + const encoding = encoding_for_model(model); + + // Encode and count tokens + const tokens = encoding.encode(content); + const tokenCount = tokens.length; + + // Clean up + encoding.free(); + + // Output token count + console.log(tokenCount); + +} catch (error) { + console.error(`Error: ${error.message}`); + process.exit(1); +} diff --git a/scripts/lint-prompting.sh b/scripts/lint-prompting.sh new file mode 100755 index 0000000..e594310 --- /dev/null +++ b/scripts/lint-prompting.sh @@ -0,0 +1,226 @@ +#!/bin/bash +# Lint agent, skill, and custom agent files for token size +# Usage: ./scripts/lint-prompting.sh [file.agent.md | file/SKILL.md] +# ./scripts/lint-prompting.sh --skills # Lint only skills +# ./scripts/lint-prompting.sh --all # Lint both agents and skills +# If no file is provided, lints all agents in agents/ + +set -euo pipefail + +# Token thresholds for agents +WARN_THRESHOLD=5000 +ERROR_THRESHOLD=7000 + +# Token thresholds for skills (higher since loaded on-demand) +SKILL_WARN_THRESHOLD=8000 +SKILL_ERROR_THRESHOLD=12000 + +# Colors for output +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +# Check if Node.js is installed +if ! command -v node &> /dev/null; then + echo -e "${RED}ERROR: Node.js is not installed${NC}" + echo "Please install Node.js from https://nodejs.org/" + exit 1 +fi + +# Check if dependencies are installed +if [ ! -d "node_modules/@dqbd/tiktoken" ]; then + echo -e "${RED}ERROR: Dependencies are not installed${NC}" + echo "Run: npm install" + exit 1 +fi + +# Function to lint a single file +lint_file() { + local file="$1" + local filename=$(basename "$file") + + if [[ ! -f "$file" ]]; then + echo -e "${RED}ERROR: File not found: $file${NC}" + return 1 + fi + + # Count tokens using Node.js script + local token_count=$(node scripts/count-tokens.js "$file" 2>/dev/null || echo "0") + + # Use standard thresholds for all agents + local warn_threshold=$WARN_THRESHOLD + local error_threshold=$ERROR_THRESHOLD + + # Check thresholds + if (( token_count >= error_threshold )); then + echo -e "${RED}✗ ERROR${NC} $filename: ${RED}${token_count} tokens${NC} (exceeds ${error_threshold} token limit)" + return 1 + elif (( token_count >= warn_threshold )); then + echo -e "${YELLOW}⚠ WARN${NC} $filename: ${YELLOW}${token_count} tokens${NC} (exceeds ${warn_threshold} token warning threshold)" + return 0 + else + echo -e "${GREEN}✓ OK${NC} $filename: ${token_count} tokens" + return 0 + fi +} + +# Function to lint a skill file +lint_skill() { + local file="$1" + local skill_name=$(basename "$(dirname "$file")") + + if [[ ! -f "$file" ]]; then + echo -e "${RED}ERROR: File not found: $file${NC}" + return 1 + fi + + # Count tokens using Node.js script + local token_count=$(node scripts/count-tokens.js "$file" 2>/dev/null || echo "0") + + # Check thresholds (skills have higher limits since loaded on-demand) + if (( token_count >= SKILL_ERROR_THRESHOLD )); then + echo -e "${RED}✗ ERROR${NC} ${skill_name}/SKILL.md: ${RED}${token_count} tokens${NC} (exceeds ${SKILL_ERROR_THRESHOLD} token limit)" + return 1 + elif (( token_count >= SKILL_WARN_THRESHOLD )); then + echo -e "${YELLOW}⚠ WARN${NC} ${skill_name}/SKILL.md: ${YELLOW}${token_count} tokens${NC} (exceeds ${SKILL_WARN_THRESHOLD} token warning threshold)" + return 0 + else + echo -e "${GREEN}✓ OK${NC} ${skill_name}/SKILL.md: ${token_count} tokens" + return 0 + fi +} + +# Function to lint all agents +lint_all_agents() { + local exit_code=0 + local agent_dir="agents" + + if [[ ! -d "$agent_dir" ]]; then + echo -e "${RED}ERROR: Directory not found: $agent_dir${NC}" + return 1 + fi + + local files=("$agent_dir"/*.agent.md) + if [[ ! -e "${files[0]}" ]]; then + echo -e "${YELLOW}WARNING: No agent files found in $agent_dir${NC}" + return 0 + fi + + for file in "${files[@]}"; do + if ! lint_file "$file"; then + exit_code=1 + fi + done + + return $exit_code +} + +# Function to lint all skills +lint_all_skills() { + local exit_code=0 + local skill_dir="skills" + + if [[ ! -d "$skill_dir" ]]; then + echo -e "${YELLOW}WARNING: Directory not found: $skill_dir${NC}" + return 0 + fi + + # Find all SKILL.md files in skills subdirectories + local skill_files=() + while IFS= read -r -d '' file; do + skill_files+=("$file") + done < <(find "$skill_dir" -name "SKILL.md" -type f -print0 2>/dev/null) + + if [[ ${#skill_files[@]} -eq 0 ]]; then + echo -e "${YELLOW}WARNING: No skill files found in $skill_dir${NC}" + return 0 + fi + + for file in "${skill_files[@]}"; do + if ! lint_skill "$file"; then + exit_code=1 + fi + done + + return $exit_code +} + +# Main logic +main() { + local exit_code=0 + local lint_agents=false + local lint_skills=false + local files_to_lint=() + + # Parse arguments + if [[ $# -eq 0 ]]; then + # No arguments: lint all agents (backward compatible) + lint_agents=true + else + for arg in "$@"; do + case "$arg" in + --skills) + lint_skills=true + ;; + --all) + lint_agents=true + lint_skills=true + ;; + *) + files_to_lint+=("$arg") + ;; + esac + done + fi + + # Lint specific files if provided + if [[ ${#files_to_lint[@]} -gt 0 ]]; then + for file in "${files_to_lint[@]}"; do + # Determine if this is a skill or agent file (only SKILL.md files use skill thresholds) + if [[ "$file" == */SKILL.md ]]; then + if ! lint_skill "$file"; then + exit_code=1 + fi + else + if ! lint_file "$file"; then + exit_code=1 + fi + fi + done + else + # Lint all agents if requested + if [[ "$lint_agents" == true ]]; then + echo "Linting all agent files in agents/" + echo "" + + if ! lint_all_agents; then + exit_code=1 + fi + fi + + # Lint all skills if requested + if [[ "$lint_skills" == true ]]; then + if [[ "$lint_agents" == true ]]; then + echo "" + fi + echo "Linting all skill files in skills/" + echo "" + + if ! lint_all_skills; then + exit_code=1 + fi + fi + fi + + echo "" + if [[ $exit_code -eq 0 ]]; then + echo -e "${GREEN}All checks passed!${NC}" + else + echo -e "${RED}Some checks failed. Please reduce token count in agent/skill files.${NC}" + fi + + exit $exit_code +} + +main "$@" diff --git a/src/test/suite/agents/platformDetection.test.ts b/src/test/suite/agents/platformDetection.test.ts index 2ea6809..0a1ef24 100644 --- a/src/test/suite/agents/platformDetection.test.ts +++ b/src/test/suite/agents/platformDetection.test.ts @@ -5,7 +5,7 @@ suite('Platform Detection Test Suite', () => { suite('getPlatformInfo', () => { test('should return a valid platform', () => { const platform = getPlatformInfo(); - assert.ok(['darwin', 'win32', 'linux'].includes(platform)); + assert.ok(['darwin', 'win32', 'linux', 'wsl'].includes(platform)); }); }); diff --git a/src/ui/utils/truncate.ts b/src/ui/utils/truncate.ts index bebbbd5..13aa019 100644 --- a/src/ui/utils/truncate.ts +++ b/src/ui/utils/truncate.ts @@ -12,5 +12,10 @@ export function truncateForDisplay(text: string, maxLength = 50): string { return normalized; } + // Minimum meaningful maxLength is 3 (for "...") + if (maxLength < 3) { + return '...'.slice(0, maxLength); + } + return normalized.slice(0, maxLength - 3) + '...'; }