diff --git a/.changeset/audit-onboarding-skill-guards.md b/.changeset/audit-onboarding-skill-guards.md new file mode 100644 index 000000000..a9bff2a99 --- /dev/null +++ b/.changeset/audit-onboarding-skill-guards.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Add defensive guards from architecture audit: (1) verify roster is populated after team creation before dispatching to coordinator, preventing empty-roster dispatch loops; (2) warn when `squad upgrade` overwrites customized built-in skills diff --git a/.changeset/docs-state-backends.md b/.changeset/docs-state-backends.md new file mode 100644 index 000000000..b391687af --- /dev/null +++ b/.changeset/docs-state-backends.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Add user-facing documentation for state backends (git-notes, orphan-branch, worktree) diff --git a/.changeset/dynamic-state-discovery.md b/.changeset/dynamic-state-discovery.md new file mode 100644 index 000000000..0a54892c6 --- /dev/null +++ b/.changeset/dynamic-state-discovery.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Replace hardcoded state artifact lists with dynamic directory scanning in externalize/internalize, preventing silent orphaning when new `.squad/` artifacts are added. diff --git a/.changeset/external-capability-loading.md b/.changeset/external-capability-loading.md new file mode 100644 index 000000000..8564320b6 --- /dev/null +++ b/.changeset/external-capability-loading.md @@ -0,0 +1,9 @@ +--- +'@bradygaster/squad-cli': minor +--- + +feat(watch): load external WatchCapabilities from .squad/capabilities/ + +Users can now define custom watch capabilities as .js files in `.squad/capabilities/`. +Each file default-exports a WatchCapability object (name, phase, preflight, execute). +Capabilities are loaded at watch startup and participate in the normal phase-based round cycle. diff --git a/.changeset/fix-cast-base-path.md b/.changeset/fix-cast-base-path.md new file mode 100644 index 000000000..60c850b6a --- /dev/null +++ b/.changeset/fix-cast-base-path.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Passes repo root to LocalAgentSource instead of .squad/ dir, preventing a double-nested .squad/.squad/agents/ lookup. In remote mode, passes paths.teamDir (team repo root) so agents are discovered from the correct location. diff --git a/.changeset/fix-copilot-message-flag.md b/.changeset/fix-copilot-message-flag.md new file mode 100644 index 000000000..3d2cff7b3 --- /dev/null +++ b/.changeset/fix-copilot-message-flag.md @@ -0,0 +1,11 @@ +--- +'@bradygaster/squad-cli': patch +'@bradygaster/squad-sdk': patch +--- + +Fix obsolete --message flag in watch and loop commands + +Replace `--message` with `-p` across all watch capabilities and loop command. +Use `copilot` directly on Windows to avoid .cmd console window issues, +fall back to `gh copilot` on other platforms. Fix TOKEN_PATH typo in +comms-teams.ts (pre-existing bug from #906). diff --git a/.changeset/monorepo-subfolder-support.md b/.changeset/monorepo-subfolder-support.md new file mode 100644 index 000000000..83eefed28 --- /dev/null +++ b/.changeset/monorepo-subfolder-support.md @@ -0,0 +1,8 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +fix: monorepo subfolder support — never run git init (#939) + +When `squad init` runs in a subfolder of a monorepo, place `.github/agents/squad.agent.md` at the git root and `.squad/` in the subfolder. Never run `git init`. diff --git a/.changeset/pid-tracker-cleanup.md b/.changeset/pid-tracker-cleanup.md new file mode 100644 index 000000000..eaf5c8dad --- /dev/null +++ b/.changeset/pid-tracker-cleanup.md @@ -0,0 +1,9 @@ +--- +'@bradygaster/squad-cli': patch +--- + +fix(watch): track child process PIDs and cleanup orphans on exit + +Prevents MCP server process leaks during long-running watch sessions. +Tracks PIDs of spawned copilot sessions, kills them on exit/crash, +and cleans up stale orphans from previous crashed runs on startup. diff --git a/.changeset/review-findings-fix.md b/.changeset/review-findings-fix.md new file mode 100644 index 000000000..45b641b97 --- /dev/null +++ b/.changeset/review-findings-fix.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +fix: address post-merge review findings — catch unknown narrowing and deprecation warnings for start/rc commands diff --git a/.changeset/shell-injection-fixes.md b/.changeset/shell-injection-fixes.md new file mode 100644 index 000000000..52a95ff33 --- /dev/null +++ b/.changeset/shell-injection-fixes.md @@ -0,0 +1,12 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Eliminate shell injection vectors in scheduler and state backend + +- Replace all `execSync` calls with `execFileSync` using explicit argv arrays (no shell interpretation) +- Refactor git helper functions to accept `string[]` instead of space-delimited strings +- Add `validateTaskRef()` for scheduler script refs (rejects null bytes, newlines) +- Add `validateStateKey()` for state backend keys (rejects null bytes, newlines, tabs, path traversal) +- Validate script task refs at manifest parse time (defense-in-depth) +- Add security-focused tests for both scheduler and state backend diff --git a/.changeset/skill-security-scanner.md b/.changeset/skill-security-scanner.md new file mode 100644 index 000000000..283f2e497 --- /dev/null +++ b/.changeset/skill-security-scanner.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Add markdown-aware skill security scanner (Phase 1) to security-review.mjs. Scans .copilot/skills and .squad/skills markdown files for embedded credentials, download-and-execute patterns, and privilege escalation commands. Includes fenced code block suppression, inline code span suppression, and placeholder token detection. Zero false positives on existing 35 skill files. diff --git a/.changeset/start-tunnel-node-pty.md b/.changeset/start-tunnel-node-pty.md new file mode 100644 index 000000000..b0d27302c --- /dev/null +++ b/.changeset/start-tunnel-node-pty.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Validate node-pty availability before side effects in squad start --tunnel diff --git a/.changeset/teams-adapter-fixes.md b/.changeset/teams-adapter-fixes.md new file mode 100644 index 000000000..6c35388d5 --- /dev/null +++ b/.changeset/teams-adapter-fixes.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Teams adapter: encode channelId in deep-link URL, log warning on icacls failure, add integration tests diff --git a/.changeset/teams-adapter-security.md b/.changeset/teams-adapter-security.md new file mode 100644 index 000000000..f6e34b3d6 --- /dev/null +++ b/.changeset/teams-adapter-security.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-sdk': patch +--- + +Teams adapter token security: tenant-scoped token cache (keyed by tenant ID hash), explicit logout() for session teardown, 15-minute device-code timeout guard, stale token cleanup on permanent auth errors, per-instance user ID cache. Migration guide for async createCommunicationAdapter change. diff --git a/.changeset/watch-p0-p1-fixes.md b/.changeset/watch-p0-p1-fixes.md new file mode 100644 index 000000000..fce11b953 --- /dev/null +++ b/.changeset/watch-p0-p1-fixes.md @@ -0,0 +1,10 @@ +--- +'@bradygaster/squad-cli': patch +--- + +fix(watch): wire missing CLI flags to config, validate --state-backend, fix auth stderr parsing (#834) + +- Wire --notify-level, --overnight-start, --overnight-end, --sentinel-file to WatchConfig and loadWatchConfig merge +- Add --state-backend flag with upfront validation against allowed values (worktree, git-notes, orphan, external) +- Fix probeCurrentGhUser() to use `gh api user -q .login` (stdout) instead of parsing `gh auth status` stderr +- Also wire authUser through loadWatchConfig merge logic (was accepted but silently dropped) \ No newline at end of file diff --git a/.changeset/watch-rate-limit-detection.md b/.changeset/watch-rate-limit-detection.md new file mode 100644 index 000000000..5d83469ac --- /dev/null +++ b/.changeset/watch-rate-limit-detection.md @@ -0,0 +1,5 @@ +--- +'@bradygaster/squad-cli': patch +--- + +Fix watch reporting "Board is clear" when GitHub API rate limit is hit. Detects rate-limit and other scan errors, shows appropriate warning instead of the misleading idle message, skips subsequent phases on failed scans, and prevents rate-limited rounds from counting as circuit-breaker successes. diff --git a/.copilot/skills/pr-lifecycle/SKILL.md b/.copilot/skills/pr-lifecycle/SKILL.md new file mode 100644 index 000000000..50456644e --- /dev/null +++ b/.copilot/skills/pr-lifecycle/SKILL.md @@ -0,0 +1,537 @@ +--- +name: "pr-lifecycle" +description: "Complete issue → PR → merge lifecycle with readiness checks" +domain: "workflow" +confidence: "high" +source: "extracted from CONTRIBUTING.md, PR_REQUIREMENTS.md, squad-ci.yml" +--- + +## Context + +This skill is the **canonical Copilot-agent lifecycle for the Squad repository**. It covers the full path from picking up a GitHub issue to merging a PR. Where older docs (templates, copilot-instructions, CONTRIBUTING.md) conflict with this skill, **this skill takes precedence** for Copilot agents. + +For advanced scenarios (worktrees, multi-repo coordination), see `.copilot/skills/git-workflow/SKILL.md`. + +## Scope + +✅ **THIS SKILL COVERS:** +- Issue pickup and branch creation +- Implementation, build, test, lint workflow +- Pre-push safety verification +- PR creation with correct target, title, body, and labels +- All 11 PR readiness checks (what they check, how to pass, how to fix) +- Post-creation maintenance (review feedback, rebasing, CI) +- Merge preconditions and cleanup + +❌ **THIS SKILL DOES NOT COVER:** +- Worktree-based parallel work (see `git-workflow` skill) +- Multi-repo coordinated PRs (see `git-workflow` skill) +- Release process / publishing (see `.squad/skills/release-process`) +- Reviewer lockout protocol (see `.copilot/skills/reviewer-protocol`) +- Architectural or security review checklists + +--- + +## Lifecycle Procedure + +### Phase 1 — Issue Pickup + +1. **Read the issue.** Understand the acceptance criteria before writing code. + +2. **Confirm capability fit.** Check your capability profile in `.squad/team.md`. 🟢 = proceed. 🟡 = proceed but flag in PR. 🔴 = comment on issue and stop. + +3. **Branch from dev:** + ```bash + git fetch origin dev + git checkout dev + git rebase origin/dev + git checkout -b squad/{issue-number}-{slug} + ``` + - Branch name format: `squad/{issue-number}-{kebab-case-slug}` + - Example: `squad/42-fix-login-validation` + - **Never** branch from `main` + +4. **Mark in-progress** (optional): + ```bash + gh issue edit {number} --add-label "status:in-progress" + ``` + +--- + +### Phase 2 — Implementation + +1. **Make your changes.** Follow the codebase conventions: + - TypeScript strict mode, no `@ts-ignore` + - ESM-only, async/await + - JSDoc on new public APIs + +2. **Build:** + ```bash + npm run build + ``` + +3. **Test:** + ```bash + npm test + ``` + +4. **Type check:** + ```bash + npm run lint + ``` + +5. **Stage specific files only:** + ```bash + git add path/to/file1.ts path/to/file2.ts + ``` + - ❌ **NEVER** `git add .`, `git add -A`, or `git commit -a` + - ✅ **ALWAYS** name each file explicitly + +6. **Pre-push safety check:** + ```bash + # Verify file count matches intent (expect ≤10 files for most fixes) + git diff --cached --stat + + # Verify NO unintended deletions + git diff --cached --diff-filter=D --name-only + ``` + If you see unexpected files or deletions, unstage them: `git reset HEAD ` + +7. **Single commit with issue reference:** + ```bash + git commit -m "Brief description of change + + Closes #{issue-number} + + Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + ``` + - If you already have multiple commits, squash: `git rebase -i origin/dev` and squash all into one + - **Never** use `git reset --soft` to squash — it picks up delta from dev and contaminates the commit + +8. **Add changeset (if required):** + A changeset is required when your PR modifies files under `packages/squad-sdk/src/` or `packages/squad-cli/src/`. + + ```bash + npx changeset add + ``` + This prompts for: which packages changed, bump type (patch/minor/major), summary. + + Or create manually at `.changeset/{descriptive-name}.md`: + ```markdown + --- + '@bradygaster/squad-cli': patch + --- + + Brief description of the change + ``` + + If the changeset creates a second commit, squash it into your main commit. + +--- + +### Phase 3 — PR Creation + +1. **Push:** + ```bash + git push -u origin squad/{issue-number}-{slug} + ``` + +2. **Create PR targeting dev:** + ```bash + gh pr create --repo bradygaster/squad --base dev \ + --title "fix: brief description (#issue-number)" \ + --body "Closes #{issue-number} + + ## Summary + What this PR does and why. + + ## Changes + - File-level description of changes + + Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + ``` + +3. **Title conventions:** + - Bug fix: `fix: description (#N)` + - Feature: `feat: description (#N)` + - Docs: `docs: description (#N)` + - Chore/infra: `chore: description (#N)` + +4. **Labels:** + - `fix`, `feat`, `docs`, or `repo-health` for type + - `squad:{agent-name}` if working as a squad member + - `skip-changelog` only with reviewer approval (escape hatch) + +5. **Scope rules by label:** + - `repo-health`: Only modify `.github/`, `scripts/`, root config, tests, docs. **Never** modify `packages/*/src/` + - `fix` or `feat`: May modify product source. Must include changeset when touching `packages/*/src/` + +--- + +### Phase 4 — PR Readiness + +An automated readiness check runs on every push and posts a checklist comment on the PR. All 11 checks must pass before review (check 11 is informational-only). + +#### Check 1: Single Commit + +| | | +|---|---| +| **What** | PR must contain exactly 1 commit | +| **Pass** | Push a single, squashed commit | +| **Fix** | `git rebase -i origin/dev` → squash all commits into one → `git push --force-with-lease` | +| **Gotcha** | Never `git reset --soft` to squash — contaminates the commit with unrelated changes | + +#### Check 2: Not in Draft + +| | | +|---|---| +| **What** | PR must not be marked as draft | +| **Pass** | Create PR as ready, or convert: `gh pr ready` | +| **Fix** | `gh pr ready {number}` or use the GitHub UI | +| **Gotcha** | The readiness check still runs on drafts but will show ❌ until you mark ready | + +#### Check 3: Branch Up to Date + +| | | +|---|---| +| **What** | PR branch must not be behind `dev` | +| **Pass** | Rebase onto latest dev before pushing | +| **Fix** | `git fetch origin dev && git rebase origin/dev && git push --force-with-lease` | +| **Gotcha** | After rebasing, the readiness check re-runs automatically on the new push | + +#### Check 4: Copilot Review + +| | | +|---|---| +| **What** | The `copilot-pull-request-reviewer` bot must post an `APPROVED` review | +| **Pass** | Wait — Copilot review is triggered automatically on PR creation/push | +| **Fix** | If Copilot hasn't reviewed after 5 minutes, push an empty commit to re-trigger: `git commit --allow-empty -m "trigger review" && git push` then squash before merge | +| **Gotcha** | Copilot review state is `APPROVED`, `CHANGES_REQUESTED`, or `COMMENTED`. Only `APPROVED` passes | + +#### Check 5: Changeset Present + +| | | +|---|---| +| **What** | PRs that modify `packages/squad-sdk/src/` or `packages/squad-cli/src/` must include a `.changeset/*.md` file or a `CHANGELOG.md` edit | +| **Pass** | Run `npx changeset add` and commit the generated file | +| **Fix** | `npx changeset add` → select affected package(s) → select bump type → write summary → `git add .changeset/ && git commit --amend --no-edit && git push --force-with-lease` | +| **Gotcha** | The `skip-changelog` label bypasses this check but requires reviewer approval. Non-source changes (docs, config, tests) don't need a changeset | + +#### Check 6: No Merge Conflicts + +| | | +|---|---| +| **What** | PR must be cleanly mergeable with the base branch | +| **Pass** | Keep branch rebased on dev | +| **Fix** | `git fetch origin dev && git rebase origin/dev` → resolve conflicts → `git push --force-with-lease` | +| **Gotcha** | GitHub may show `null` mergeability briefly while computing — the check treats this as passing | + +#### Check 7: Scope Clean + +| | | +|---|---| +| **What** | Warns if PR includes `.squad/` or `docs/proposals/` files | +| **Pass** | Don't include team state or proposal files in product PRs | +| **Fix** | Remove unintended files: `git reset HEAD .squad/ docs/proposals/` then amend your commit | +| **Gotcha** | This check is **informational only** — it always passes but flags attention. Including these files is OK if intentional (e.g., updating agent history) | + +#### Check 8: Copilot Threads Resolved + +| | | +|---|---| +| **What** | All review threads opened by `copilot-pull-request-reviewer` must be resolved | +| **Pass** | Address each Copilot comment, then click "Resolve conversation" in the GitHub UI | +| **Fix** | Go to the PR's "Files changed" tab → find unresolved Copilot threads → fix the code or explain why no change is needed → click "Resolve conversation" | +| **Gotcha** | Outdated threads (on code that's since changed) are automatically skipped. Only active, unresolved threads block | + +#### Check 9: CI Passing + +| | | +|---|---| +| **What** | All CI check runs (excluding the readiness check itself) must be green | +| **Pass** | Fix any build, test, or lint failures and push | +| **Fix** | Read the failing check's logs (`gh run view {run-id} --log-failed`), fix the issue, push | +| **Gotcha** | Pending/in-progress checks also show ❌. Wait for all checks to complete before evaluating. The readiness check re-runs after CI completes via `workflow_run` trigger | + +#### Check 10: Issue Linked + +| | | +|---|---| +| **What** | PR body or commit message must reference an issue (`Closes #N`, `Fixes #N`, `Resolves #N`, or `Part of #N`) | +| **Pass** | Include `Closes #N` in PR body or commit message | +| **Fix** | Edit PR body to add `Closes #{issue-number}`, or amend commit message | +| **Gotcha** | Case-insensitive matching. Only closing keywords are recognized | + +#### Check 11: Protected Files (informational) + +| | | +|---|---| +| **What** | Warns when zero-dependency bootstrap files are modified (always passes) | +| **Pass** | Always passes — this is informational only | +| **Fix** | If flagged, verify the changed bootstrap file still has zero external dependencies | +| **Gotcha** | Protected files are listed in `copilot-instructions.md`. The check warns but does not block | + +--- + +### Phase 5 — Post-Creation Maintenance + +1. **Handling Copilot review feedback:** + - Read each comment carefully + - Fix valid issues in your code + - Resolve each thread after addressing it + - Amend your single commit: `git commit --amend --no-edit && git push --force-with-lease` + +2. **Rebasing when behind dev:** + ```bash + git fetch origin dev + git rebase origin/dev + # Resolve any conflicts + git push --force-with-lease + ``` + +3. **Re-running CI:** + - CI re-runs automatically on every push + - To re-run without changes: `gh run rerun {run-id} --failed` + +4. **If readiness check is stale:** + - The readiness check re-runs on push and after CI completes + - If it's stale, push a trivial fix or empty commit to re-trigger + +--- + +### Phase 6 — Merge & Cleanup + +1. **Merge preconditions:** + - All 11 readiness checks pass (✅ across the board) + - Human reviewer has approved (or maintainer merges directly) + - No open blocking conversations + +2. **Who merges:** + - Agents do **not** merge PRs themselves + - Maintainers (bradygaster or designated reviewers) merge via GitHub UI + - The repo uses squash merge, so commit history is clean regardless + +3. **Post-merge cleanup:** + ```bash + git checkout dev + git pull origin dev + git branch -d squad/{issue-number}-{slug} + git push origin --delete squad/{issue-number}-{slug} + ``` + +4. **Verify issue auto-close:** + - If PR body contains `Closes #{N}`, the issue closes automatically on merge + - If not, manually close: `gh issue close {number}` + +--- + +## Examples + +### Example: Bug fix PR (no source changes) + +```bash +# Branch +git fetch origin dev && git checkout dev && git rebase origin/dev +git checkout -b squad/610-fix-broken-link + +# Fix +# ... edit docs/some-file.md ... + +# Validate +npm run build && npm test + +# Commit (no changeset needed — docs only) +git add docs/some-file.md +git diff --cached --stat # verify: 1 file +git commit -m "docs: fix broken link in contributing guide + +Closes #610 + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + +# Push and PR +git push -u origin squad/610-fix-broken-link +gh pr create --repo bradygaster/squad --base dev \ + --title "docs: fix broken link (#610)" \ + --body "Closes #610" +``` + +### Example: SDK feature PR (source changes) + +```bash +# Branch +git fetch origin dev && git checkout dev && git rebase origin/dev +git checkout -b squad/42-add-profile-api + +# Implement +# ... edit packages/squad-sdk/src/profile/index.ts ... +# ... edit packages/squad-sdk/src/index.ts (re-export) ... + +# Validate +npm run build && npm test && npm run lint + +# Changeset (required — touches packages/squad-sdk/src/) +npx changeset add +# Select: @bradygaster/squad-sdk, minor, "Add profile API" + +# Stage and commit +git add packages/squad-sdk/src/profile/index.ts packages/squad-sdk/src/index.ts .changeset/ +git diff --cached --stat # verify file count +git diff --cached --diff-filter=D --name-only # verify no deletions +git commit -m "feat: add profile API + +Closes #42 + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" + +# Push and PR +git push -u origin squad/42-add-profile-api +gh pr create --repo bradygaster/squad --base dev \ + --title "feat: add profile API (#42)" \ + --body "Closes #42 + +## Summary +Adds profile resolution API to the SDK. + +## Changes +- New module: packages/squad-sdk/src/profile/ +- Re-exported from barrel file" +``` + +--- + +## Anti-Patterns + +- ❌ Branching from `main` (always branch from `dev`) +- ❌ Targeting `main` with PRs (always target `dev`) +- ❌ Using `git add .` or `git add -A` (stage specific files only) +- ❌ Using `git commit -a` (same risk as broad staging) +- ❌ Using `git reset --soft` to squash (contaminates commit with dev delta) +- ❌ Force-pushing to `dev` or `main` (only force-push your own feature branch) +- ❌ Merging your own PR (maintainers merge) +- ❌ Skipping the changeset when source files changed (CI will fail) +- ❌ Self-resolving Copilot threads without addressing the feedback +- ❌ Pushing >1 commit without squashing (readiness check will flag it) +- ❌ Including `.squad/` files in product PRs without intention (scope check warns) +- ❌ Mixing product and infrastructure changes in one PR (create separate PRs) + +--- + +## Readiness Check Gaps & Recommendations + +After analyzing `.github/workflows/squad-ci.yml`, three gaps were identified. Gaps 1 and 3 are now implemented (checks 10 and 11). Gap 2 is deferred. + +### Gap 1: Issue Linkage Check (Check 10) — IMPLEMENTED + +**Problem:** Nothing verifies that the PR body or commit message references an issue (`Closes #N` or `Part of #N`). Orphan PRs are hard to trace. + +**Recommendation:** Add `checkIssueLinkage(prBody, commitMessages)` to `pr-readiness.mjs`. + +```javascript +/** + * Check: Issue linkage. + * @param {string} prBody — PR description text + * @param {Array<{ commit: { message: string } }>} commits + * @returns {{ pass: boolean, detail: string }} + */ +export function checkIssueLinkage(prBody, commits) { + const issuePattern = /(closes|fixes|resolves|part of)\s+#\d+/i; + const bodyHasRef = issuePattern.test(prBody || ''); + const commitHasRef = (commits || []).some( + (c) => issuePattern.test(c.commit?.message || '') + ); + if (bodyHasRef || commitHasRef) { + return { pass: true, detail: 'Issue reference found' }; + } + return { + pass: false, + detail: 'No issue reference — add `Closes #N` to PR body or commit message', + }; +} +``` + +**Integration point:** After check 2 (draft status), before check 3 (branch freshness). Data is already available from the commits and PR body fetched in the orchestrator. + +### Gap 2: No Required Checks Presence Verification + +**Problem:** The CI status check (check 9) verifies that existing checks are green, but doesn't verify that the *expected* set of checks actually ran. If a workflow is misconfigured or skipped, the PR could pass with zero CI checks. + +**Recommendation:** Add `checkRequiredChecksPresent(checkRuns, files)` to `pr-readiness.mjs`. + +```javascript +/** Minimum required check names that must appear for source PRs. */ +export const REQUIRED_CHECKS = ['Squad CI / test']; + +/** + * Check: Required CI checks are present. + * @param {Array<{ name: string }>} checkRuns + * @param {Array<{ filename: string }>} files + * @returns {{ pass: boolean, detail: string }} + */ +export function checkRequiredChecksPresent(checkRuns, files) { + const touchesSource = (files || []).some( + (f) => SOURCE_PATTERN.test(f.filename) + ); + if (!touchesSource) { + return { pass: true, detail: 'No source changes — required checks not enforced' }; + } + const checkNames = new Set((checkRuns || []).map((cr) => cr.name)); + const missing = REQUIRED_CHECKS.filter((name) => !checkNames.has(name)); + if (missing.length > 0) { + return { + pass: false, + detail: `Required check(s) not found: ${missing.join(', ')}`, + }; + } + return { pass: true, detail: 'All required checks present' }; +} +``` + +**Integration point:** After check 9 (CI status). Uses the same `checkRuns` and `files` data already fetched. + +### Gap 3: Protected File Change Detection (Check 11) — IMPLEMENTED + +**Problem:** The repo has zero-dependency bootstrap files that must never import external packages (documented in `copilot-instructions.md`). The `squad-repo-health.yml` workflow runs a bootstrap protection check, but the PR readiness comment doesn't surface it — contributors don't see the warning until they check the separate workflow. + +**Recommendation:** Add `checkProtectedFiles(files)` to `pr-readiness.mjs` as an **informational** check (always passes, like scope clean). + +```javascript +/** Bootstrap files that must remain zero-dependency. */ +export const PROTECTED_FILES = [ + 'packages/squad-cli/src/cli/core/detect-squad-dir.ts', + 'packages/squad-cli/src/cli/core/errors.ts', + 'packages/squad-cli/src/cli/core/gh-cli.ts', + 'packages/squad-cli/src/cli/core/output.ts', + 'packages/squad-cli/src/cli/core/history-split.ts', +]; + +/** + * Check: Protected file changes (informational). + * @param {Array<{ filename: string }>} files + * @returns {{ pass: boolean, detail: string }} + */ +export function checkProtectedFiles(files) { + const touched = (files || []).filter( + (f) => PROTECTED_FILES.includes(f.filename) + ); + if (touched.length === 0) { + return { pass: true, detail: 'No protected bootstrap files changed' }; + } + return { + pass: true, + detail: `⚠️ ${touched.length} protected bootstrap file(s) changed: ${touched.map((f) => f.filename.split('/').pop()).join(', ')} — verify zero-dependency constraint`, + }; +} +``` + +**Integration point:** After check 7 (scope clean). Uses the same `files` data. Informational only — warns but doesn't block. + +### Summary of Recommended Changes + +| Check | Type | Blocks PR? | Status | +|-------|------|------------|--------| +| Issue linkage (Check 10) | Hard gate | Yes | Implemented | +| Required checks present | Hard gate | Yes | Deferred | +| Protected file warning (Check 11) | Informational | No | Implemented | + +All three use data that `pr-readiness.mjs` already fetches — no new API calls needed. Total addition: ~60 lines of check functions + ~10 lines of orchestration wiring. diff --git a/.copilot/skills/protected-files/SKILL.md b/.copilot/skills/protected-files/SKILL.md new file mode 100644 index 000000000..1b35373f9 --- /dev/null +++ b/.copilot/skills/protected-files/SKILL.md @@ -0,0 +1,53 @@ +--- +name: "protected-files" +description: "Zero-dependency bootstrap files that must never import npm packages or SDK code" +domain: "dependency-safety" +confidence: "high" +source: "earned — CLI startup crashes when bootstrap files import SDK code" +--- + +## Context + +The CLI (`squad-cli`) has bootstrap utilities that run **before** the Squad SDK is loaded. If these files import SDK code (e.g., `FSStorageProvider`, anything from `squad-sdk`), the CLI breaks at startup — no helpful error, just a crash. + +This skill applies when: +- Touching any file in `packages/squad-cli/src/cli/core/` +- Running sweeping refactors (e.g., "convert all `fs` calls to `StorageProvider`") +- Adding new bootstrap utilities + +## Protected File List + +| File | Purpose | +|------|---------| +| `packages/squad-cli/src/cli/core/detect-squad-dir.ts` | Finds `.squad/` directory at startup — runs before SDK init | +| `packages/squad-cli/src/cli/core/errors.ts` | Error classes (`SquadError`, `fatal()`) — used by all CLI entry points | +| `packages/squad-cli/src/cli/core/gh-cli.ts` | GitHub CLI wrapper — uses only `node:child_process` and `node:util` | +| `packages/squad-cli/src/cli/core/output.ts` | Color/emoji console output — pure ANSI codes, zero imports | +| `packages/squad-cli/src/cli/core/history-split.ts` | Separates portable knowledge from project data — pure string logic | + +## Rules + +- ❌ **NEVER** convert these files to use `FSStorageProvider`, `StorageProvider`, or any SDK abstraction +- ❌ **NEVER** add `import` or `require` statements referencing packages outside `node:*` built-ins +- ✅ **ONLY** use `node:fs`, `node:path`, `node:child_process`, `node:util`, and other Node.js built-in modules +- ✅ **DO** check this list before sweeping refactors +- ✅ **LOOK** for `— zero dependencies` markers in file headers as a signal + +## SDK/CLI Package Boundary + +The `packages/squad-cli/src/cli/core/` directory contains a mix of early-startup bootstrap utilities and later SDK-dependent modules. The protected list above is the **authoritative set** of zero-dependency bootstrap files. If you need to add SDK imports to another `core/` file, verify it is not in the protected list and confirm the SDK is loaded at that point in the startup sequence. + +## Anti-Patterns + +- Converting all `fs` calls to `StorageProvider` without checking this list first +- Adding `import { X } from '@bradygaster/squad-sdk'` to a bootstrap file +- Assuming every file in `core/` can safely import SDK code + +## Adding New Bootstrap Utilities + +When adding a new file that runs before SDK init: +1. Add it to the Protected File List table above +2. Write a matching zero-dependency regression test (see `detect-squad-dir-zero-deps.test.ts` for the pattern) +3. Add `— zero dependencies` marker in the file header + +Regression tests guard these files, but **prevention is better than detection**. diff --git a/.github/actions/setup-squad-node/action.yml b/.github/actions/setup-squad-node/action.yml index b1bc0d54a..569d01a5b 100644 --- a/.github/actions/setup-squad-node/action.yml +++ b/.github/actions/setup-squad-node/action.yml @@ -1,6 +1,6 @@ # Composite action: Setup Node.js with npm cache and retry logic # Created to DRY up the repeated setup-node + npm install retry pattern -# used across squad-ci.yml, ci-rerun.yml, squad-npm-publish.yml, +# used across squad-ci.yml, squad-npm-publish.yml, # and squad-insider-publish.yml. # # Usage: diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 1a3813a33..01e18dfad 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -19,6 +19,7 @@ You are **Squad (Coordinator)** — the orchestrator for this project's AI team. - You may NOT generate domain artifacts (code, designs, analyses) — spawn an agent - You may NOT bypass reviewer approval on rejected work - You may NOT invent facts or assumptions — ask the user or spawn an agent who knows + - You may NOT do work yourself — ALWAYS delegate to a team member, even for small tasks. The only exception is Direct Mode (status checks, factual questions, and simple answers from context — see Response Mode Selection). Check: Does `.squad/team.md` exist? (fall back to `.ai-team/team.md` for repos migrating from older installs) - **No** → Init Mode @@ -104,7 +105,7 @@ The `union` merge driver keeps all lines from both sides, which is correct for a **If you wrote code, generated artifacts, or produced domain work without dispatching to an agent, you violated this rule. The coordinator ROUTES — it does not BUILD. No exceptions.** -**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root into every spawn prompt as `TEAM_ROOT` and the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. +**On every session start:** Run `git config user.name` to identify the current user, and **resolve the team root** (see Worktree Awareness). Store the team root — all `.squad/` paths must be resolved relative to it. Pass the team root and the current datetime (from `` in your system context) into every spawn prompt as `TEAM_ROOT` and `CURRENT_DATETIME` respectively. Pass the current user's name into every agent spawn prompt and Scribe log so the team always knows who requested the work. Check `.squad/identity/now.md` if it exists — it tells you what the team was last focused on. Update it if the focus has shifted. **⚡ Context caching:** After the first message in a session, `team.md`, `routing.md`, and `registry.json` are already in your context. Do NOT re-read them on subsequent messages — you already have the roster, routing rules, and cast names. Only re-read if the user explicitly modifies the team (adds/removes members, changes routing). @@ -330,6 +331,7 @@ description: "{emoji} {Name}: {brief task summary}" prompt: | You are {Name}, the {Role} on this project. TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} WORKTREE_PATH: {worktree_path} WORKTREE_MODE: {true|false} **Requested by:** {current user name} @@ -348,7 +350,7 @@ prompt: | ⚠️ RESPONSE ORDER: After ALL tool calls, write a plain text summary as FINAL output. ``` -For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. {question} TEAM ROOT: {team_root}"` +For read-only queries, use the explore agent: `agent_type: "explore"` with `"You are {Name}, the {Role}. CURRENT_DATETIME: {current_datetime} — {question} TEAM ROOT: {team_root}"` ### Per-Agent Model Selection @@ -499,7 +501,7 @@ The `sql` tool is **CLI-only**. It does not exist on VS Code, JetBrains, or GitH MCP (Model Context Protocol) servers extend Squad with tools for external services — Trello, Aspire dashboards, Azure, Notion, and more. The user configures MCP servers in their environment; Squad discovers and uses them. -> **Full patterns:** Read `.squad/skills/mcp-tool-discovery/SKILL.md` for discovery patterns, domain-specific usage, graceful degradation. Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. +> **Config details:** Read `.squad/templates/mcp-config.md` for config file locations, sample configs, and authentication notes. #### Detection @@ -623,15 +625,17 @@ Squad and all spawned agents may be running inside a **git worktree** rather tha **How the Coordinator resolves the team root (on every session start):** -1. Run `git rev-parse --show-toplevel` to get the current worktree root. -2. Check if `.squad/` exists at that root (fall back to `.ai-team/` for repos that haven't migrated yet). +1. **Check CWD first** — does `.squad/` exist in the current working directory? + - **Yes** → Team root = CWD. This handles monorepos where `.squad/` lives in a subfolder. +2. If not, run `git rev-parse --show-toplevel` to get the current worktree root. +3. Check if `.squad/` exists at that root (fall back to `.ai-team/` for repos that haven't migrated yet). - **Yes** → use **worktree-local** strategy. Team root = current worktree root. - **No** → use **main-checkout** strategy. Discover the main working tree: ``` git worktree list --porcelain ``` The first `worktree` line is the main working tree. Team root = that path. -3. The user may override the strategy at any time (e.g., *"use main checkout for team state"* or *"keep team state in this worktree"*). +4. The user may override the strategy at any time (e.g., *"use main checkout for team state"* or *"keep team state in this worktree"*). **Passing the team root to agents:** - The Coordinator includes `TEAM_ROOT: {resolved_path}` in every spawn prompt. @@ -769,6 +773,7 @@ prompt: | {paste contents of .squad/agents/{name}/charter.md here} TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} All `.squad/` paths are relative to this root. PERSONAL_AGENT: {true|false} # Whether this is a personal agent @@ -815,6 +820,7 @@ prompt: | Do the work. Respond as {Name}. ⚠️ OUTPUT: Report outcomes in human terms. Never expose tool internals or SQL. + ⚠️ DATES: When writing dates in any file (decisions, history, logs), use ONLY the CURRENT_DATETIME value above. Never infer or guess the date. AFTER work: 1. APPEND to .squad/agents/{name}/history.md under "## Learnings": @@ -871,6 +877,7 @@ description: "📋 Scribe: Log session & merge decisions" prompt: | You are the Scribe. Read .squad/agents/scribe/charter.md. TEAM ROOT: {team_root} + CURRENT_DATETIME: {current_datetime} SPAWN MANIFEST: {spawn_manifest} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a99f6ced6..d09a2ed3d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,159 +1,60 @@ # Copilot Coding Agent — Squad Instructions -You are working on a project that uses **Squad**, an AI team framework. When picking up issues autonomously, follow these guidelines. - -## Git Safety — Mandatory Rules - -**These rules are non-negotiable. Violating them risks deleting production source code.** - -### Staging -- ❌ **NEVER** use `git add .` or `git add -A` — these stage unintended deletions from incomplete working trees -- ❌ **NEVER** use `git commit -a` — same risk -- ✅ **ALWAYS** stage specific files: `git add path/to/file1.ts path/to/file2.ts` -- ✅ **ALWAYS** review before committing: run `git diff --cached --stat` and verify the file count matches your intent - -### Pushing -- ❌ **NEVER** push directly to `dev` or `main` — always open a PR -- ❌ **NEVER** force push (`git push --force` or `--force-with-lease`) to shared branches -- ✅ **ALWAYS** work on a feature branch: `git checkout -b squad/{issue-number}-{slug}` -- ✅ **ALWAYS** open a PR: `gh pr create --base dev --draft` - -### Pre-Push Checklist -Before pushing any commit, verify: -1. `git diff --cached --stat` — file count matches intent (expect ≤10 files for most fixes) -2. `git diff --cached --diff-filter=D --name-only` — NO unintended deletions -3. `npm run build` — build succeeds with your changes -4. Commit message references the issue: `Closes #N` - -### Branch Contamination Prevention -- When creating PR branches, always branch from the latest upstream dev: `git fetch upstream && git checkout dev && git rebase upstream/dev && git checkout -b ` -- NEVER use `git reset --soft` to squash on branches behind dev — it picks up all the delta between your branch and dev, contaminating the commit -- To squash safely: use `git rebase -i` or amend the single commit with `git commit --amend` -- Before committing, ALWAYS verify the diff: `git diff --cached --stat` should show ONLY your intended files -- If you see unexpected files in the diff, unstage them: `git reset HEAD ` - -### Red Flags — STOP and Ask -If you see any of these, STOP immediately and comment on the issue asking for guidance: -- More than 20 files in your diff -- ANY file deletions you didn't explicitly intend -- Changes outside the scope of your assigned issue - -## Protected Files — Zero External Dependencies - -**Some files MUST only use Node.js built-in modules. Do NOT add npm packages, SDK imports, or any non-`node:*` dependencies to them.** - -These are bootstrap utilities that run **before** the Squad SDK is loaded. If they import SDK code (e.g., `FSStorageProvider`, anything from `squad-sdk`), the CLI breaks at startup. - -### Protected file list -| File | Purpose | -|------|---------| -| `packages/squad-cli/src/cli/core/detect-squad-dir.ts` | Finds `.squad/` directory at startup — runs before SDK init | -| `packages/squad-cli/src/cli/core/errors.ts` | Error classes (`SquadError`, `fatal()`) — used by all CLI entry points | -| `packages/squad-cli/src/cli/core/gh-cli.ts` | GitHub CLI wrapper — uses only `node:child_process` and `node:util` | -| `packages/squad-cli/src/cli/core/output.ts` | Color/emoji console output — pure ANSI codes, zero imports | -| `packages/squad-cli/src/cli/core/history-split.ts` | Separates portable knowledge from project data — pure string logic | - -### Rules -- ❌ **NEVER** convert these files to use `FSStorageProvider`, `StorageProvider`, or any SDK abstraction -- ❌ **NEVER** add `import` or `require` statements referencing packages outside `node:*` built-ins -- ✅ **ONLY** use `node:fs`, `node:path`, `node:child_process`, `node:util`, and other Node.js built-in modules -- ✅ **DO** check this list before sweeping refactors (e.g., "convert all fs calls to StorageProvider") -- ✅ **LOOK** for `— zero dependencies` markers in file headers as a signal - -### Why this matters -Regression tests guard these files (`detect-squad-dir-zero-deps.test.ts` verifies zero external dependencies), but **prevention is better than detection**. A broken bootstrap means the entire CLI fails to start — no helpful error, just a crash. - -> **When adding new bootstrap utilities**, add them to this table and write a matching zero-dependency regression test. - -### SDK/CLI package boundary -The CLI (`squad-cli`) depends on the SDK (`squad-sdk`). Some CLI files run **before** the SDK is fully loaded. The `packages/squad-cli/src/cli/core/` directory contains a mix of early-startup/bootstrap utilities and later SDK-dependent modules, so treat every file in it with extra caution. The protected list above is the authoritative set of zero-dependency bootstrap files. If you need to add SDK imports to another `core/` file, verify it is not in the protected list above and confirm the SDK is loaded at that point in the startup sequence. - -## Sweeping Refactor Rules - -When applying a codebase-wide pattern change (e.g., "convert all `fs` calls to `StorageProvider`"), follow these steps **before** converting each file: - -1. **Check the Protected Files list above.** If the file is listed, do NOT convert it. -2. **Scan for zero-dependency markers.** Look for `— zero dependencies` in the file's header comment. If present, do NOT convert it. -3. **Verify imports resolve.** When adding `import { X } from '@bradygaster/squad-sdk'`, confirm `X` is actually exported from the SDK's barrel file (`packages/squad-sdk/src/index.ts`). Unresolved imports cause build failures. -4. **Never convert ALL files blindly.** Some files have specific constraints documented in their headers or in this instructions file. Read before you refactor. -5. **Test after each logical group.** Don't convert 30 files and then run the build — convert in small batches and verify each one compiles. +You are working on **Squad**, an AI team framework. Follow these guidelines for autonomous issue work. ## Team Context -Before starting work on any issue: - -1. Read `.squad/team.md` for the team roster, member roles, and your capability profile. +1. Read `.squad/team.md` for roster, roles, and your capability profile. 2. Read `.squad/routing.md` for work routing rules. -3. If the issue has a `squad:{member}` label, read that member's charter at `.squad/agents/{member}/charter.md` to understand their domain expertise and coding style — work in their voice. +3. If the issue has a `squad:{member}` label, read `.squad/agents/{member}/charter.md` — work in their voice. ## Capability Self-Check -Before starting work, check your capability profile in `.squad/team.md` under the **Coding Agent → Capabilities** section. - -- **🟢 Good fit** — proceed autonomously. -- **🟡 Needs review** — proceed, but note in the PR description that a squad member should review. -- **🔴 Not suitable** — do NOT start work. Instead, comment on the issue: - ``` - 🤖 This issue doesn't match my capability profile (reason: {why}). Suggesting reassignment to a squad member. - ``` +Check your profile in `.squad/team.md` under **Coding Agent → Capabilities**: 🟢 proceed, 🟡 proceed + flag for review in PR, 🔴 stop and comment on issue suggesting reassignment. ## Branch Naming -Use the squad branch convention: -``` -squad/{issue-number}-{kebab-case-slug} -``` -Example: `squad/42-fix-login-validation` +`squad/{issue-number}-{kebab-case-slug}` — Example: `squad/42-fix-login-validation` -## PR Guidelines +## Git Safety — Mandatory -When opening a PR: -- Reference the issue: `Closes #{issue-number}` -- If the issue had a `squad:{member}` label, mention the member: `Working as {member} ({role})` -- If this is a 🟡 needs-review task, add to the PR description: `⚠️ This task was flagged as "needs review" — please have a squad member review before merging.` -- Follow any project conventions in `.squad/decisions.md` +- ❌ NEVER `git add .`, `git add -A`, or `git commit -a` — stage specific files only +- ❌ NEVER push to `dev` or `main` directly — always open a PR +- ❌ NEVER force push to shared branches +- ✅ Branch from latest dev: `git fetch origin && git checkout dev && git pull origin dev && git checkout -b ` +- ✅ Before committing: `git diff --cached --stat` (file count matches intent) and `git diff --cached --diff-filter=D --name-only` (no unintended deletions) +- ✅ `npm run build` must pass before pushing. Commit message must reference `Closes #N`. +- 🛑 STOP and ask if: >20 files in diff, unintended deletions, or out-of-scope changes -## PR Review Skills +## Protected Files + +When touching files in `packages/squad-cli/src/cli/core/`, read `.copilot/skills/protected-files/SKILL.md` first. Some bootstrap files must use only Node.js built-ins — no npm packages or SDK imports. -When reviewing or creating PRs, consult these skills for domain-specific checklists: +## Sweeping Refactors -- **Reviewer Protocol:** `.copilot/skills/reviewer-protocol/SKILL.md` — rejection workflow, lockout rules -- **Architectural Review:** `.copilot/skills/architectural-review/SKILL.md` — module boundaries, dependency safety, pattern consistency -- **Security Review:** `.copilot/skills/security-review/SKILL.md` — credentials, injection, workflow permissions, supply chain +Before codebase-wide changes, check the Protected Files skill and scan for `— zero dependencies` markers in file headers. Convert in small batches; verify each compiles. Confirm SDK imports resolve against `packages/squad-sdk/src/index.ts`. -Read the relevant skill(s) before submitting or reviewing a PR that touches their domain. +## PR Guidelines + +- Reference the issue: `Closes #{issue-number}` +- If `squad:{member}` labeled, mention: `Working as {member} ({role})` +- If 🟡 task, add: `⚠️ Needs squad member review before merging.` +- Consult `.squad/decisions.md` for project conventions ## PR Scope Rules -Scope is determined by PR labels, not by branch name. Continue using the squad branch convention: `squad/{issue-number}-{slug}`. -- **Repo-health PRs** (label `repo-health`): Only modify `.github/`, `scripts/`, root config files, test files, and docs. NEVER modify files under `packages/*/src/`. -- **Product PRs** (label `fix` or `feat`): May modify product source code. Must include changesets when required by the rules below. -- If a task requires both infrastructure and product changes, create separate PRs. +- **`repo-health`** PRs: Only `.github/`, `scripts/`, root configs, tests, docs. NEVER `packages/*/src/`. +- **`fix`/`feat`** PRs: May modify product source. Requires changeset if touching `packages/*/src/`. +- Split infrastructure + product changes into separate PRs. ## Changeset Requirement -Any PR that modifies files under `packages/squad-cli/src/` or `packages/squad-sdk/src/` MUST include a changeset file. - -- Run `npx changeset add` and select the affected package(s) -- Or manually create `.changeset/{descriptive-name}.md` with format: - ``` - --- - '@bradygaster/squad-cli': patch - --- - Brief description of the change - ``` -- Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes -- The `changelog-gate` CI check will fail without this -- Escape hatch: add the `skip-changelog` label (use sparingly) +PRs modifying `packages/squad-cli/src/` or `packages/squad-sdk/src/` MUST include a `.changeset/{name}.md` file (patch/minor/major). The `changelog-gate` CI check enforces this. Escape hatch: `skip-changelog` label. -## Automated PR Nudge +## PR Review Skills -The **PR Nudge** workflow (`.github/workflows/squad-pr-nudge.yml`) runs on weekdays at 2pm UTC and posts actionable comments on open PRs that have been stale for 7+ days. It diagnoses specific blockers — failing CI checks, unresolved review threads, missing approvals, outdated branches, and draft status — so PR authors know exactly what to do next. Draft PRs get a 14-day grace period. The workflow won't nudge the same PR more than once per week. +Before submitting or reviewing PRs, consult: `.copilot/skills/reviewer-protocol/SKILL.md`, `.copilot/skills/architectural-review/SKILL.md`, `.copilot/skills/security-review/SKILL.md`. ## Decisions -If you make a decision that affects other team members, write it to: -``` -.squad/decisions/inbox/copilot-{brief-slug}.md -``` -The Scribe will merge it into the shared decisions file. +Team decisions go to `.squad/decisions/inbox/copilot-{brief-slug}.md` — Scribe merges them. diff --git a/.github/workflows/ci-rerun.yml b/.github/workflows/ci-rerun.yml deleted file mode 100644 index 3652ce909..000000000 --- a/.github/workflows/ci-rerun.yml +++ /dev/null @@ -1,153 +0,0 @@ -# CI Rerun — Manual workflow for re-running CI on fork PRs or PRs that need -# a fresh CI pass without push. Useful when: -# - Fork PRs don't trigger CI automatically (security restriction) -# - A PR needs re-testing after an infrastructure flake -# - You need to validate a PR against current base branch state -# Usage: Actions → CI Rerun (Manual) → enter PR number → Run workflow -name: CI Rerun (Manual) - -on: - workflow_dispatch: - inputs: - pr_number: - description: 'PR number to test' - required: true - type: string - -permissions: - contents: read - statuses: write - -jobs: - test: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Fetch PR metadata - id: pr - env: - GH_TOKEN: ${{ github.token }} - run: | - PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}) - HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha') - HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.ref') - HEAD_REPO=$(echo "$PR_DATA" | jq -r '.head.repo.full_name') - BASE_REF=$(echo "$PR_DATA" | jq -r '.base.ref') - echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" - echo "head_ref=$HEAD_REF" >> "$GITHUB_OUTPUT" - echo "head_repo=$HEAD_REPO" >> "$GITHUB_OUTPUT" - echo "base_ref=$BASE_REF" >> "$GITHUB_OUTPUT" - echo "Testing PR #${{ inputs.pr_number }}: $HEAD_REPO/$HEAD_REF → $BASE_REF (SHA: $HEAD_SHA)" - - - name: Set pending status - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \ - --method POST \ - --field state=pending \ - --field context="Squad CI / rerun" \ - --field description="Running CI rerun..." \ - --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - - uses: actions/checkout@v4 - with: - ref: refs/pull/${{ inputs.pr_number }}/merge - continue-on-error: true - id: checkout_merge - - - name: Fallback — checkout PR head directly - if: steps.checkout_merge.outcome == 'failure' - uses: actions/checkout@v4 - with: - repository: ${{ steps.pr.outputs.head_repo }} - ref: ${{ steps.pr.outputs.head_ref }} - - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - - - name: Fix stale lockfile entries - run: | - node -e " - const fs = require('fs'); - const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); - const pkgs = lock.packages || {}; - const stale = Object.keys(pkgs).filter(k => - k.includes('/node_modules/@bradygaster/squad-') && - pkgs[k].resolved && pkgs[k].resolved.startsWith('https://') - ); - if (stale.length) { - stale.forEach(k => { console.log('Removing: ' + k); delete pkgs[k]; }); - fs.writeFileSync('package-lock.json', JSON.stringify(lock, null, 2) + '\n'); - } else { - console.log('Lockfile clean'); - } - " - - - name: Install dependencies - run: | - success=false - for i in 1 2 3; do - if npm install; then - success=true - break - fi - echo "Retry $i/3 — npm install failed, retrying in 5s..." - sleep 5 - done - if [ "$success" = false ]; then - echo "::error::npm install failed after 3 attempts" - exit 1 - fi - - - name: Install docs dependencies - run: | - success=false - for i in 1 2 3; do - if npm ci; then - success=true - break - fi - echo "Retry $i/3 — npm ci failed, retrying in 5s..." - sleep 5 - done - if [ "$success" = false ]; then - echo "::error::npm ci failed after 3 attempts" - exit 1 - fi - working-directory: docs - - - name: Install Playwright browsers - run: npx playwright install chromium --with-deps - - - name: Build - run: npm run build - - - name: Run tests - run: npm test - - - name: Set success status - if: success() - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \ - --method POST \ - --field state=success \ - --field context="Squad CI / rerun" \ - --field description="All checks passed" \ - --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - - - name: Set failure status - if: failure() - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.head_sha }} \ - --method POST \ - --field state=failure \ - --field context="Squad CI / rerun" \ - --field description="CI rerun failed — check logs" \ - --field target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index a7e7b9d0b..50d72797f 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -9,21 +9,22 @@ on: permissions: contents: read + pull-requests: read -# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +# Prevent parallel runs from competing for resources concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # ── Path filter for conditional job execution ────────────────────────── - # Detects which areas of the repo changed so downstream jobs can skip - # when their inputs haven't changed (e.g., skip docs-quality on code-only PRs). changes: runs-on: ubuntu-latest timeout-minutes: 3 outputs: docs: ${{ steps.filter.outputs.docs }} + code: ${{ steps.filter.outputs.code }} + workflows: ${{ steps.filter.outputs.workflows }} steps: - uses: actions/checkout@v4 with: @@ -44,6 +45,16 @@ jobs: else echo "docs=false" >> "$GITHUB_OUTPUT" fi + if echo "$CHANGED" | grep -qvE '^(docs/|README\.md|\.markdownlint|\.cspell|cspell\.json)'; then + echo "code=true" >> "$GITHUB_OUTPUT" + else + echo "code=false" >> "$GITHUB_OUTPUT" + fi + if echo "$CHANGED" | grep -qE '^\.github/workflows/'; then + echo "workflows=true" >> "$GITHUB_OUTPUT" + else + echo "workflows=false" >> "$GITHUB_OUTPUT" + fi docs-quality: needs: changes @@ -52,44 +63,38 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: 22 - - name: Install docs tools run: | success=false for i in 1 2 3; do - if npm install --no-save markdownlint-cli2 cspell; then - success=true - break - fi - echo "Retry $i/3 — npm install failed, retrying in 5s..." - sleep 5 + if npm install --no-save markdownlint-cli2 cspell; then success=true; break; fi + echo "Retry $i/3 — npm install failed, retrying in 5s..."; sleep 5 done - if [ "$success" = false ]; then - echo "::error::npm install failed after 3 attempts" - exit 1 - fi - + [ "$success" = true ] || { echo "::error::npm install failed after 3 attempts"; exit 1; } - name: Lint docs markdown run: npx markdownlint-cli2 - - name: Spell check docs run: npx cspell --no-progress --dot "docs/src/content/**/*.md" "README.md" test: + needs: changes + # Fail-open: run test if changes job failed (don't let path filter break testing) + if: >- + always() && !cancelled() + && (github.event_name == 'push' + || needs.changes.result != 'success' + || needs.changes.outputs.code == 'true') runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: 22 cache: 'npm' - - name: Fix stale lockfile entries run: | node -e " @@ -107,46 +112,27 @@ jobs: console.log('Lockfile clean'); } " - - name: Install dependencies run: | success=false for i in 1 2 3; do - if npm install; then - success=true - break - fi - echo "Retry $i/3 — npm install failed, retrying in 5s..." - sleep 5 + if npm install; then success=true; break; fi + echo "Retry $i/3 — npm install failed, retrying in 5s..."; sleep 5 done - if [ "$success" = false ]; then - echo "::error::npm install failed after 3 attempts" - exit 1 - fi - + [ "$success" = true ] || { echo "::error::npm install failed after 3 attempts"; exit 1; } - name: Install docs dependencies run: | success=false for i in 1 2 3; do - if npm ci; then - success=true - break - fi - echo "Retry $i/3 — npm ci failed, retrying in 5s..." - sleep 5 + if npm ci; then success=true; break; fi + echo "Retry $i/3 — npm ci failed, retrying in 5s..."; sleep 5 done - if [ "$success" = false ]; then - echo "::error::npm ci failed after 3 attempts" - exit 1 - fi + [ "$success" = true ] || { echo "::error::npm ci failed after 3 attempts"; exit 1; } working-directory: docs - - name: Install Playwright browsers run: npx playwright install chromium --with-deps - - name: "🔒 Source tree canary check" run: | - echo "Verifying critical source files exist..." MISSING=0 for f in \ "packages/squad-sdk/src/index.ts" \ @@ -163,7 +149,6 @@ jobs: exit 1 fi echo "✅ All critical source files present" - - name: "🔒 Large deletion guard" if: github.event_name == 'pull_request' run: | @@ -180,573 +165,161 @@ jobs: fi env: GH_TOKEN: ${{ github.token }} - - name: Build run: npm run build - - name: Run tests run: npm test - # ════════════════════════════════════════════════════════════════════════ - # Skip Labels Reference - # ──────────────────────────────────────────────────────────────────────── - # The following PR labels can be used to bypass specific health gates. - # Add them via the GitHub UI or `gh pr edit --add-label