Skip to content

feat(ci): add workspace integrity, prerelease guard, and export smoke gates#688

Closed
diberry wants to merge 16 commits intobradygaster:devfrom
diberry:squad/ci-health-gates
Closed

feat(ci): add workspace integrity, prerelease guard, and export smoke gates#688
diberry wants to merge 16 commits intobradygaster:devfrom
diberry:squad/ci-health-gates

Conversation

@diberry
Copy link
Copy Markdown
Collaborator

@diberry diberry commented Mar 29, 2026

Summary

Adds 3 new CI validation gates to \squad-ci.yml\ motivated by the PR #640 prerelease version incident where npm silently resolved a stale published SDK instead of the local workspace copy.

Gate 1: \workspace-integrity\

  • Verifies lockfile has no stale registry entries for workspace packages
  • Catches: npm resolving published registry copy instead of local workspace symlink
  • Cost: Zero-install (reads \package-lock.json\ only)
  • Flag: \�ars.SQUAD_WORKSPACE_CHECK\ / label: \skip-workspace-check\

Gate 2: \prerelease-version-guard\

  • Blocks prerelease version suffixes (-build, -alpha, -beta, -rc) from merging to dev/main
  • Catches: Forgotten prerelease suffixes that break semver range resolution
  • Cost: Zero-install (reads \packages/*/package.json\ only)
  • Flag: \�ars.SQUAD_VERSION_CHECK\ / label: \skip-version-check\

Gate 3: ^[xport-smoke-test\

  • Verifies all subpath exports resolve to built artifacts after SDK build
  • Catches: Missing \dist/\ files for declared exports (complements ^[xports-map-check)
  • Cost: Lightweight install + SDK build (~30s), only runs when SDK files change
  • Flag: \�ars.SQUAD_EXPORT_SMOKE\ / label: \skip-export-smoke\

Design

  • All gates follow existing CI patterns: feature flags, skip labels, three-dot diff, ::error::\ annotations
  • Gates 1 & 2 are zero-install (fast, no
    pm ci\ needed)
  • Gate 3 only triggers on SDK source/config changes and does its own lightweight build

Closes #114


Fork PR: diberry#115

Merge Order

These 3 PRs form a dependency chain — merge in this order:

  1. PR: fix(sdk,cli): remove prerelease version suffixes (from fork PR feat: Extract cold-path sections from squad.agent.md (#98) #116) — fixes dev branch versions
  2. PR: feat(ci): add workspace integrity, prerelease guard, and export smoke gates (from fork PR chore: Remove .ai-team/ deprecation banner (#71) #115) — adds CI enforcement
  3. PR: docs(skill): add versioning policy (from fork PR chore: standardize timestamps to ISO 8601 UTC #117) — documents the rules

Copilot bot and others added 16 commits March 27, 2026 13:40
Fork-specific workflow skills for PR hygiene:
- fork-first-pipeline: review on fork before opening upstream PRs
- bleed-check: twice-daily cross-branch audit for stowaway files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds Step 5.5 REBASE with guidance on rebasing feature branches against origin/dev to avoid full-file rewrites on shared files. Includes Shared File Strategy for surgical additions and When Rebase Fails recovery steps. Updates anti-patterns table to flag 'Skip rebase before upstream'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, conventions)

- Fork-first-pipeline: Added serial branch operations warning, force-add note for gitignored skills, stale comments check
- Bleed-check: Added high-risk shared files callout, convention gate checks, CI path debugging pattern

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Dual Reviewer Gate: both Flight and FIDO must approve
- Convention Issues Are Blockers: /docs/ prefix, double blanks, table order, whitespace
- Review→Fix→Re-review Loop: both reviewers must re-review after fixes
- Reviewer Lockout: locked authors cannot self-fix after rejection
- Known PAO Quirks: watch for .squad/ files, /docs/ prefix, verification before push
- Review Comments: formal PR comment format and verdict options
- Tamir Reviewer Rule: only on upstream PRs with source material
- Commit Hygiene: one commit, amend fixups, force-push safely, verify before push

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a PR completes the full fork pipeline (Flight+FIDO approved, bleed check passed, squashed, rebased, moved to upstream targeting Brady's dev), the upstream PR should be undrafted immediately.

The upstream PR is the final presentation — draft PRs signal incomplete work, but at this stage all iteration is finished on the fork.

Add gh pr ready command to Step 7 (UPSTREAM) as the final action after opening the upstream PR.

Add anti-pattern entry for leaving upstream PRs in draft.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Validates that the squad-main-guard.yml workflow was properly removed:
- v0.5.4 migration in index.js correctly targets and deletes the guard
- TEMPLATE_MANIFEST has no reference to squad-main-guard.yml
- SDK init and CLI init/upgrade never create the guard workflow
- No remaining workflow templates block .squad/ paths on push to main

10 tests covering guard removal, template manifest, init/upgrade safety,
and workflow template scanning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Updated fork-first-pipeline skill with PR bradygaster#640 retrospective learnings
- .squad/ decisions and state synced
- Merged inbox decisions into decisions.md and decisions-archive.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Prevents .squad/ stowaways on feature branches.
Learned from 3 incidents on PR bradygaster#640.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- .github/PR_REQUIREMENTS.md: versioned canonical spec (from #106 PRD)
- .github/PULL_REQUEST_TEMPLATE.md: author-facing checklist

Addresses Challenger FATAL finding: spec must be versioned, not mutable issue.
Part of #106.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds .github/PR_REQUIREMENTS.md (versioned spec with 6 categories, CRUD-on-CLI/SDK user-facing definition, waiver process, exemptions) and .github/PULL_REQUEST_TEMPLATE.md (author-facing checklist). Part 1 of 2 for repo health -- #104 will automate enforcement.

Closes Phase 2 of #106

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Part 2 of 2 for repo health. Adds two automated CI enforcement gates to squad-ci.yml:

1. CHANGELOG gate -- requires CHANGELOG.md update when SDK/CLI source changes
2. Exports map check -- verifies package.json exports match barrel files

Both feature-flagged (vars.SQUAD_CHANGELOG_CHECK, vars.SQUAD_EXPORTS_CHECK) with skip labels. Includes test coverage for check-exports-map.mjs.

Refs #104

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Closes #103

Adds samples-build CI job that validates all 11 samples compile when SDK changes.
- Loops samples/ directories with npm install/build/test
- Feature-flagged via skip-samples-build label
- 15-minute timeout, --ignore-scripts, npm cache
- Accepted Copilot suggestions: cache-dependency-path, workspace root install

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… gates

Adds 3 new CI validation gates motivated by the PR bradygaster#640 prerelease
version incident where npm silently resolved a stale registry SDK.

- workspace-integrity: verifies lockfile has no stale registry entries
  for workspace packages (zero-install, reads lockfile only)
- prerelease-version-guard: blocks prerelease version suffixes from
  merging to dev/main (zero-install, reads package.json only)
- export-smoke-test: verifies all subpath exports resolve to built
  artifacts after SDK build (lightweight install+build)

All gates follow existing patterns: feature flags (vars.SQUAD_*),
skip labels, three-dot diff for change detection, ::error:: annotations.

Closes #114

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Skip Labels Reference comment block listing all available skip labels (PAO, Flight)
- Add local testing instructions to each health gate (Flight, FIDO)
- Document change-detection regex patterns for future maintainers (FIDO)
- Enhance export smoke test with dynamic import() validation (EECOM)
- Add test PR creation hints to gate comments (FIDO)

Closes #115

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove duplicate cache-dependency-path in export-smoke-test setup-node step
- Remove false-positive else-if branch that flagged workspace packages lacking link:true
  (npm lockfile v3 workspace entries under node_modules/ use resolved:file: not link:true)

Closes #115

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 29, 2026 16:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds additional CI “health gates” to reduce workspace/version/export regressions (motivated by prior incidents), plus supporting scripts/tests and documentation/process updates.

Changes:

  • Add new CI jobs for workspace lockfile integrity, prerelease version blocking, exports-map validation, and subpath export smoke testing.
  • Add scripts + Vitest coverage for exports-map checking and guard workflow removal regression protection.
  • Add PR process artifacts (PR template/requirements) and new/updated docs pages.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
.github/workflows/squad-ci.yml Adds multiple new CI gates (workspace integrity, prerelease guard, exports map check, export smoke test, samples build, changelog gate) and skip-label handling.
scripts/check-exports-map.mjs New script to compare SDK barrel directories vs package.json exports.
test/check-exports-map.test.ts Ensures the exports-map checker script runs and produces structured output.
test/guard-removal.test.ts Adds regression tests ensuring the old “main guard” workflow isn’t recreated by init/upgrade/migrations/templates.
docs/src/content/docs/features/context-hygiene.md Updates Nap description and links to new concept page.
docs/src/content/docs/concepts/nap-vs-compact.md New concept page explaining Nap vs /compact.
.github/PULL_REQUEST_TEMPLATE.md Adds a structured PR template with testing/docs/exports checklists.
.github/PR_REQUIREMENTS.md Adds a versioned PR requirements spec doc.
.squad/skills/fork-first-pipeline/SKILL.md New skill documenting a fork-first PR workflow.
.copilot/skills/bleed-check/SKILL.md New skill documenting bleed-check audit process.
.squad/decisions.md Moves/records decision entries (including CI safety items) and updates decision log content.
.squad/decisions-archive.md Archives older decision entries removed from .squad/decisions.md.
.squad/decisions/inbox/retro-copilot-git-safety.md Removes inbox entry (content moved elsewhere).
.squad/decisions/inbox/booster-ci-deletion-guard.md Removes inbox entry (content moved elsewhere).

Comment on lines +385 to +386
if ! (cd "$sample_dir" && npm install --ignore-scripts 2>&1); then
echo "::error::[$sample] npm install failed"
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The samples gate uses npm install inside each sample directory even though many samples have package-lock.json. Using npm ci when a lockfile is present makes installs reproducible and reduces flakiness from dependency drift (and usually speeds CI up). Consider branching: npm ci if package-lock.json exists, else npm install.

Suggested change
if ! (cd "$sample_dir" && npm install --ignore-scripts 2>&1); then
echo "::error::[$sample] npm install failed"
if [ -f "$sample_dir/package-lock.json" ]; then
INSTALL_CMD="npm ci --ignore-scripts"
else
INSTALL_CMD="npm install --ignore-scripts"
fi
if ! (cd "$sample_dir" && $INSTALL_CMD 2>&1); then
echo "::error::[$sample] dependency installation failed"

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +128
# ════════════════════════════════════════════════════════════════════════
# 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 <label>`.
#
# skip-changelog — skip the changelog-gate job
# skip-exports-check — skip the exports-map-check job
# skip-samples-ci — skip the samples-build job
# skip-workspace-check — skip the workspace-integrity job
# skip-version-check — skip the prerelease-version-guard job
# skip-export-smoke — skip the export-smoke-test job
# large-deletion-approved — bypass the large-deletion guard (test job)
#
# These labels are compatible with sync-squad-labels.yml — add them
# to the label sync config to auto-create them in new repos.
# ════════════════════════════════════════════════════════════════════════
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description focuses on CI gates, but this workflow file also introduces additional non-CI process artifacts elsewhere in the PR (PR template/requirements and new skill docs). If those are intentionally in-scope, the PR description/title should mention them; otherwise consider splitting them into a separate PR to keep CI changes reviewable and revertible.

Copilot uses AI. Check for mistakes.
```

In the interactive shell, use `/compact` for the same effect.
> **Note:** For session-level conversation compression, use `/compact` in the Copilot CLI interactive shell. See [Nap vs /compact](/concepts/nap-vs-compact) for the difference between team state cleanup and session transcript compression.
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This internal docs link uses an absolute route (/concepts/nap-vs-compact) while most docs in this repo use relative .md links (e.g., ../concepts/github-workflow.md). Using relative links is generally more robust across differing site base paths and keeps link style consistent within the docs set.

Suggested change
> **Note:** For session-level conversation compression, use `/compact` in the Copilot CLI interactive shell. See [Nap vs /compact](/concepts/nap-vs-compact) for the difference between team state cleanup and session transcript compression.
> **Note:** For session-level conversation compression, use `/compact` in the Copilot CLI interactive shell. See [Nap vs /compact](../concepts/nap-vs-compact.md) for the difference between team state cleanup and session transcript compression.

Copilot uses AI. Check for mistakes.
<!-- Approach taken. Key design decisions and trade-offs. -->

### Testing
<!-- What was tested and how. "npm run preflight" output or CI green. -->
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template mentions "npm run preflight", but the repo doesn’t define a preflight script. This can confuse contributors filling out the template; consider referencing the actual commands used here (e.g., npm run build && npm test) or adding a real preflight script if that’s intended.

Suggested change
<!-- What was tested and how. "npm run preflight" output or CI green. -->
<!-- What was tested and how. "npm run build && npm test" output or CI green. -->

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +29
\\\
BRANCH → FORK PR → PREFLIGHT → REVIEW → FIX → BLEED CHECK → CLEAN → UPSTREAM → DONE
\\\

### Step 1: BRANCH
Create a feature branch locally:
\\\ash
git checkout -b squad/{issue-number}-{slug}
\\\

### Step 2: FORK PR
Push to fork and open PR **against your fork's dev branch**:
\\\ash
git push origin {branch-name}
gh pr create --base dev --draft # Opens on fork/dev, not upstream
\\\
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These \\\ / \\\bash blocks aren’t valid Markdown fences and include a stray control character before ash, so the commands won’t render as code blocks. Replace them with standard triple-backtick fences (bash ... ), and remove the backslashes/control character throughout the skill.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +45
Query:
\\\ash
gh pr list --author diberry --repo bradygaster/squad --base dev --state open
\\\

## Process

### 1. List PRs
Fetch all open PRs from diberry targeting bradygaster/squad.

### 2. For Each PR: Check File List
Retrieve the file list:
\\\ash
gh pr view {pr-number} --repo bradygaster/squad --json files
\\\
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These \\\bash / \\\ blocks aren’t valid Markdown fences and include a stray control character before ash, so the example commands won’t render correctly. Use standard triple-backtick code fences (bash ... ), and remove the backslashes/control character throughout this skill file.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +31
const srcEntries = readdirSync(SRC_DIR, { withFileTypes: true });
const barrelDirs = srcEntries
.filter((entry) => entry.isDirectory())
.filter((entry) => existsSync(join(SRC_DIR, entry.name, 'index.ts')))
.map((entry) => entry.name);

const missing = [];

for (const dir of barrelDirs) {
const exportKey = `./${dir}`;
if (!exportsMap[exportKey]) {
missing.push({ dir, expectedKey: exportKey });
}
}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check-exports-map.mjs currently fails if any top-level src/<dir>/index.ts barrel lacks a corresponding exports["./<dir>"] entry. The SDK already has existing barrel dirs (e.g. remote/, platform/, roles/, streams/, upstream/) without exports entries, so this gate will block the next PR that touches packages/squad-sdk/src/ unless those legacy gaps are addressed. Consider scoping the check to only new barrel dirs introduced in the PR (e.g., compute added src/*/index.ts dirs via git diff base...head), or maintain an explicit allowlist/denylist of public barrels.

Copilot uses AI. Check for mistakes.
Comment on lines +738 to +765
for (const [subpath, targets] of Object.entries(exportsMap)) {
const importPath = subpath === '.'
? '@bradygaster/squad-sdk'
: '@bradygaster/squad-sdk/' + subpath.slice(2);
const filePath = typeof targets === 'string'
? targets
: (targets.import || targets.default);
if (!filePath) {
failures.push({ subpath, importPath, error: 'No import target defined' });
continue;
}
const resolvedPath = path.resolve('packages/squad-sdk', filePath);

// Phase 1: File existence check
if (!fs.existsSync(resolvedPath)) {
failures.push({ subpath, importPath, filePath, error: 'File not found: ' + resolvedPath });
continue;
}

// Phase 2: Dynamic import() — verifies the module actually loads
// without syntax errors, missing dependencies, or broken re-exports.
try {
await import(pathToFileURL(resolvedPath).href);
passed++;
console.log(' ✅ ' + importPath + ' → ' + filePath + ' (exists + imports OK)');
} catch (e) {
failures.push({ subpath, importPath, filePath, error: 'import() failed: ' + e.message });
}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The export smoke test computes a package subpath specifier (@bradygaster/squad-sdk/...) but then imports the built file via file://... instead. That bypasses Node’s exports resolution rules, so a malformed/unsupported exports entry key could still pass as long as the target file exists. Importing via the computed package specifier would more accurately validate real consumer behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +173
LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name' 2>/dev/null || echo "")
if echo "$LABELS" | grep -q "skip-changelog"; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping CHANGELOG gate (skip-changelog label present)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ github.token }}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These label checks use gh pr view ... but the workflow’s top-level permissions: currently only grants contents: read. With restricted permissions, the GITHUB_TOKEN often cannot read PR metadata via the API, causing gh pr view to fail and skip labels to be ignored. Prefer using ${{ toJSON(github.event.pull_request.labels.*.name) }} (no API call) like other jobs here, or add pull-requests: read to the workflow permissions.

Suggested change
LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name' 2>/dev/null || echo "")
if echo "$LABELS" | grep -q "skip-changelog"; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping CHANGELOG gate (skip-changelog label present)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ github.token }}
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
if echo "$LABELS" | grep -q '"skip-changelog"'; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Skipping CHANGELOG gate (skip-changelog label present)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

Copilot uses AI. Check for mistakes.
@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Mar 29, 2026

Superseded by new PR with clean branch from upstream/dev (no fork-specific commits).

@diberry diberry closed this Mar 29, 2026
robzelt pushed a commit to robzelt/squad that referenced this pull request Apr 1, 2026
Wraps InputPrompt in bordered Box (borderStyle=round, borderColor=cyan) for Copilot/Claude CLI style input zone. Includes NO_COLOR degradation and layout refinement.

Closes bradygaster#679
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants