From bf295adcd7d5b52b37919a6fb09fc08e4ea9b20c Mon Sep 17 00:00:00 2001 From: "Dina Berry (She/her)" Date: Tue, 31 Mar 2026 16:11:48 -0700 Subject: [PATCH 1/9] feat(ci): add concurrency controls to 5 workflows (Phase 3 item A1) Add concurrency blocks with cancel-in-progress to squad-ci, squad-heartbeat, squad-triage, squad-label-enforce, and squad-issue-assign workflows. Scope: .github/workflows/ only (squad repo CI). Template workflows for customer repos are a separate product concern. Test: 20 assertions covering all 5 workflows. Refs: diberry/squad#122 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/squad-ci.yml | 5 ++ .github/workflows/squad-heartbeat.yml | 5 ++ .github/workflows/squad-issue-assign.yml | 5 ++ .github/workflows/squad-label-enforce.yml | 5 ++ .github/workflows/squad-triage.yml | 5 ++ test/ci-concurrency.test.cjs | 105 ++++++++++++++++++++++ 6 files changed, 130 insertions(+) create mode 100644 test/ci-concurrency.test.cjs diff --git a/.github/workflows/squad-ci.yml b/.github/workflows/squad-ci.yml index 773754e39..85819ab24 100644 --- a/.github/workflows/squad-ci.yml +++ b/.github/workflows/squad-ci.yml @@ -10,6 +10,11 @@ on: permissions: contents: read +# CI Hardening Phase 3 item A1: 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 diff --git a/.github/workflows/squad-heartbeat.yml b/.github/workflows/squad-heartbeat.yml index b5c642a92..0e3826b0c 100644 --- a/.github/workflows/squad-heartbeat.yml +++ b/.github/workflows/squad-heartbeat.yml @@ -30,6 +30,11 @@ permissions: contents: read pull-requests: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: heartbeat: runs-on: ubuntu-latest diff --git a/.github/workflows/squad-issue-assign.yml b/.github/workflows/squad-issue-assign.yml index 3badc21a3..ff3f309be 100644 --- a/.github/workflows/squad-issue-assign.yml +++ b/.github/workflows/squad-issue-assign.yml @@ -8,6 +8,11 @@ permissions: issues: write contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: assign-work: # Only trigger on squad:{member} labels (not the base "squad" label) diff --git a/.github/workflows/squad-label-enforce.yml b/.github/workflows/squad-label-enforce.yml index 633d220df..d1bb96788 100644 --- a/.github/workflows/squad-label-enforce.yml +++ b/.github/workflows/squad-label-enforce.yml @@ -8,6 +8,11 @@ permissions: issues: write contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: enforce: runs-on: ubuntu-latest diff --git a/.github/workflows/squad-triage.yml b/.github/workflows/squad-triage.yml index 9dd890b6c..613d3da02 100644 --- a/.github/workflows/squad-triage.yml +++ b/.github/workflows/squad-triage.yml @@ -8,6 +8,11 @@ permissions: issues: write contents: read +# CI Hardening Phase 3 item A1: Prevent parallel runs from competing for resources +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.ref }} + cancel-in-progress: true + jobs: triage: if: github.event.label.name == 'squad' diff --git a/test/ci-concurrency.test.cjs b/test/ci-concurrency.test.cjs new file mode 100644 index 000000000..e08ba8378 --- /dev/null +++ b/test/ci-concurrency.test.cjs @@ -0,0 +1,105 @@ +/** + * TDD test for CI Hardening Phase 3 item A1: + * Concurrency controls on all event-driven workflows. + * + * Validates that squad-ci, squad-heartbeat, squad-triage, + * squad-label-enforce, and squad-issue-assign workflows all have + * concurrency settings to prevent resource waste and race conditions. + * + * Scope: .github/workflows/ only (squad repo CI). + * Template workflows for customer repos are a separate product concern. + * + * Refs: diberry/squad#122 (Phase 3 item A1) + */ +const { describe, it } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const REPO_ROOT = path.resolve(__dirname, '..'); +const WORKFLOWS_DIR = path.join(REPO_ROOT, '.github', 'workflows'); + +// Workflows that must have concurrency controls (per issue #122 item A1) +const CONCURRENCY_REQUIRED_WORKFLOWS = [ + 'squad-ci.yml', + 'squad-heartbeat.yml', + 'squad-triage.yml', + 'squad-label-enforce.yml', + 'squad-issue-assign.yml', +]; + +// Issue-triggered workflows must use github.event.issue.number for unique concurrency groups +const ISSUE_TRIGGERED_WORKFLOWS = [ + 'squad-heartbeat.yml', + 'squad-triage.yml', + 'squad-label-enforce.yml', + 'squad-issue-assign.yml', +]; + +// PR-triggered workflows use github.ref (unique per PR) +const PR_TRIGGERED_WORKFLOWS = [ + 'squad-ci.yml', +]; + +function readWorkflow(filename) { + const filePath = path.join(WORKFLOWS_DIR, filename); + if (!fs.existsSync(filePath)) return null; + return fs.readFileSync(filePath, 'utf8'); +} + +describe('CI Hardening A1: Concurrency controls on workflows', () => { + for (const workflow of CONCURRENCY_REQUIRED_WORKFLOWS) { + it(`${workflow} has a concurrency block`, () => { + const content = readWorkflow(workflow); + if (!content) return; + assert.ok( + /^concurrency:/m.test(content), + `${workflow} must have a top-level concurrency: block` + ); + }); + + it(`${workflow} uses workflow+ref concurrency group`, () => { + const content = readWorkflow(workflow); + if (!content) return; + assert.ok( + content.includes("github.workflow") && (content.includes("github.event.issue.number") || content.includes("github.ref")), + `${workflow} concurrency group must use github.workflow and a unique identifier (issue number or ref)` + ); + }); + + it(`${workflow} has cancel-in-progress: true`, () => { + const content = readWorkflow(workflow); + if (!content) return; + assert.ok( + /cancel-in-progress:\s*true/m.test(content), + `${workflow} must have cancel-in-progress: true` + ); + }); + } + + describe('Semantic: issue-triggered workflows use issue-specific concurrency', () => { + for (const workflow of ISSUE_TRIGGERED_WORKFLOWS) { + it(`${workflow} uses github.event.issue.number`, () => { + const content = readWorkflow(workflow); + if (!content) return; + assert.ok( + content.includes("github.event.issue.number"), + `${workflow} must use github.event.issue.number for issue-specific concurrency` + ); + }); + } + }); + + describe('Semantic: PR-triggered workflows use ref-based concurrency', () => { + for (const workflow of PR_TRIGGERED_WORKFLOWS) { + it(`${workflow} does not use github.event.issue.number`, () => { + const content = readWorkflow(workflow); + if (!content) return; + assert.ok( + !content.includes("github.event.issue.number"), + `${workflow} must NOT use github.event.issue.number (PR refs are already unique)` + ); + }); + } + }); +}); From 99c17cbe6b635a7ffcf6f5d7a653b3f80da2717d Mon Sep 17 00:00:00 2001 From: "Dina Berry (She/her)" Date: Sun, 29 Mar 2026 22:29:46 -0700 Subject: [PATCH 2/9] docs: proposal for contributor guide improvements Audited all contributor-facing files on dev branch and identified 8 gaps in external contributor experience. Proposes 7 deliverables prioritized by maintainer time savings: P1: Issue templates, good-first-issue curation, .squad/ explainer P2: CODE_OF_CONDUCT.md, contributor FAQ, README contributing section P3: SECURITY.md typo fix Goal: contributors self-serve from docs instead of asking Brady/Tamir the same questions repeatedly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../contributor-guide-improvements.md | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 docs/proposals/contributor-guide-improvements.md diff --git a/docs/proposals/contributor-guide-improvements.md b/docs/proposals/contributor-guide-improvements.md new file mode 100644 index 000000000..53d756d47 --- /dev/null +++ b/docs/proposals/contributor-guide-improvements.md @@ -0,0 +1,242 @@ +# Proposal: Contributor Guide Improvements + +> **Author:** Flight (Lead) +> **Date:** 2025-07-24 +> **Status:** Draft — awaiting team review +> **Goal:** External contributors can be successful with issues and PRs without requiring repeated guidance from Brady and Tamir. + +--- + +## Executive Summary + +Squad has strong internal process documentation (CONTRIBUTING.md, PR_REQUIREMENTS.md, PR template) but significant gaps for **external contributors** — people who discover the project, want to help, and need to find their footing without hand-holding. This proposal identifies 8 gaps and recommends 7 concrete deliverables, prioritized by impact on maintainer time savings. + +--- + +## 1. Current State Audit + +### What Exists Today (dev branch) + +| File | Status | Quality | +|------|--------|---------| +| `CONTRIBUTING.md` | ✅ Exists | **Strong** — 300 lines covering setup, build, test, branch strategy, changesets, code style, common tasks. Well-structured. | +| `CONTRIBUTORS.md` | ✅ Exists | **Good** — lists AI team, community contributors, Insider Program. Good recognition. | +| `.github/PULL_REQUEST_TEMPLATE.md` | ✅ Exists | **Strong** — What/Why/How/Testing/Docs/Exports/Breaking/Waivers sections with checkboxes. | +| `.github/PR_REQUIREMENTS.md` | ✅ Exists | **Excellent** — 240 lines, canonical spec for PR requirements by category (a–f), waivers, exceptions. | +| `SECURITY.md` | ✅ Exists | **Adequate** — directs to GitHub Private Vulnerability Reporting. Minor typo ("manor" → "manner"). | +| `.github/copilot-instructions.md` | ✅ Exists | **Strong** — git safety rules, team context, capability self-check for Copilot coding agent. | +| `README.md` | ✅ Exists | **Good** — quick start, install, usage. Points to CONTRIBUTING.md. | +| `docs/proposals/` | ✅ Exists | 7 existing proposals. Healthy proposal-first culture. | + +### What's Missing + +| Gap | Impact | Who Suffers | +|-----|--------|-------------| +| **No issue templates** (`.github/ISSUE_TEMPLATE/`) | Contributors file unstructured issues; maintainers spend time asking for reproduction steps, versions, environment | Brady, Tamir, triagers | +| **No CODE_OF_CONDUCT.md** | Standard OSS expectation missing. Some contributors may hesitate without one. GitHub community profile incomplete | Community trust | +| **No "good first issue" issues** | Label exists (`good first issue`) but **zero issues** currently tagged. New contributors have no on-ramp | New contributors | +| **No `.squad/` explainer in contributor docs** | External contributors don't know what `.squad/` is, why it exists, or that they should never modify it in PRs | External contributors who include `.squad/` changes | +| **Branch strategy buried** | Branch info is in CONTRIBUTING.md §Branch Strategy but easy to miss. PR #633 (external) was opened against wrong base — empty body, zero comments, closed | External contributors | +| **No contributor FAQ / troubleshooting** | Common stumbling blocks (changeset prompts, fork sync, CI failures) require maintainer intervention | All external contributors | +| **PR #639 pattern** | External contributor submitted quality work but it targeted a feature being removed (#665). No way for contributors to know what's "safe to work on" | Contributors who pick up work | +| **`status:contributor-invited` label underused** | 8 issues tagged historically, only 2 currently open. No documented workflow for how contributors claim these | Contributors looking for work | + +### Friction Patterns from Community PRs + +Only **4 external PRs** in the last 100 (out of 100 total) — very low external contribution rate: + +| PR | Contributor | Outcome | Friction | +|----|------------|---------|----------| +| #608 | @eric-vanartsdalen | ✅ Merged | Clean — security policy revision | +| #592 | @joniba | ✅ Merged | Clean — hiring wiring guide, high-quality | +| #639 | @gaburn | ❌ Closed | Worked on REPL feature being removed (#665). No way to know. Brady redirected kindly but work was wasted. | +| #633 | @JerrettDavis | ❌ Closed | Empty PR body, no description, opened against `dev` — likely a test PR or confusion about process | + +**Key insight:** The successful external PRs (#608, #592) came from contributors who already had context. The failed ones (#639, #633) show the "cold start" problem — new contributors lack the context to pick good work and submit correctly. + +--- + +## 2. Proposed Changes + +### P1 — High Impact, Low Effort (do first) + +#### 2a. GitHub Issue Templates (`.github/ISSUE_TEMPLATE/`) + +**What:** Create 4 structured issue templates: +- `bug_report.yml` — version, environment, reproduction steps, expected vs actual behavior +- `feature_request.yml` — problem statement, proposed solution, alternatives considered +- `documentation.yml` — what's missing or wrong, where, suggested improvement +- `config.yml` — blank issue escape hatch + links to Discussions for questions + +**Who it helps:** Brady and Tamir (fewer clarification round-trips), triagers (structured data), reporters (guided through what to include). + +**Effort:** ~2 hours. YAML form-based templates (GitHub native). + +**Details:** +- Use YAML form format (not markdown) for required fields — GitHub enforces completion +- Bug template: require version (`squad version` output), Node version, OS, reproduction steps +- Feature template: require problem statement before solution (prevents "just add X" issues) +- Add `type:bug`, `type:feature`, `type:docs` auto-labels per template + +--- + +#### 2b. "Good First Issue" Curation Sprint + +**What:** Audit open issues and tag 5–10 with `good first issue`. Create a documented workflow for maintaining the label. + +**Who it helps:** New contributors looking for an on-ramp. Reduces "where do I start?" questions. + +**Effort:** ~1 hour to tag + 30 min to document the curation workflow. + +**Details:** +- Candidates: documentation gaps, typo fixes, test additions, small CLI improvements +- Each `good first issue` should include a "Getting Started" comment with: + - Which files to look at + - What the acceptance criteria are + - Which squad member to tag for questions +- Document the curation workflow in CONTRIBUTING.md: who tags, when, criteria + +--- + +#### 2c. `.squad/` Directory Explainer Section in CONTRIBUTING.md + +**What:** Add a clear section explaining what `.squad/` is, why it exists in the repo, and that **external contributors should never modify `.squad/` files in their PRs**. + +**Who it helps:** Every external contributor. Prevents accidental `.squad/` changes in PRs. + +**Effort:** ~30 minutes. + +**Proposed content:** +```markdown +## The .squad/ Directory + +The `.squad/` directory contains Squad's AI team state — agent charters, decision history, +routing rules, and accumulated learnings. It is managed exclusively by the Squad team. + +**For external contributors:** +- ❌ Do NOT modify any files in `.squad/` in your PRs +- ❌ Do NOT include `.squad/` changes in your commits +- ✅ You can READ `.squad/decisions.md` to understand team conventions +- ✅ You can READ agent charters to understand domain ownership + +If your PR includes `.squad/` changes, you'll be asked to remove them before review. +``` + +--- + +### P2 — Medium Impact, Medium Effort + +#### 2d. CODE_OF_CONDUCT.md + +**What:** Adopt the Contributor Covenant (v2.1), the standard for OSS projects. Customized with Squad-specific enforcement contacts. + +**Who it helps:** Community trust. GitHub community profile completion. Sets behavioral expectations. + +**Effort:** ~30 minutes (adopt standard, customize contact info). + +**Details:** +- Use Contributor Covenant v2.1 (industry standard, used by Node.js, Rails, etc.) +- Enforcement: Brady as primary contact +- Link from CONTRIBUTING.md and README.md + +--- + +#### 2e. Contributor FAQ / Troubleshooting Section + +**What:** Add a "Common Issues" section to CONTRIBUTING.md covering the questions maintainers answer repeatedly. + +**Who it helps:** All external contributors. Direct time savings for Brady and Tamir. + +**Effort:** ~1 hour. + +**Proposed topics:** +- "My PR CI is failing" — how to read CI output, common causes (missing changeset, build errors, lint failures) +- "Which branch do I target?" — always `dev`, never `main`. With a bold callout box. +- "Do I need a changeset?" — yes, always. How `npx changeset add` works. What to pick for patch/minor/major. +- "How do I know what's safe to work on?" — look for `good first issue`, `status:contributor-invited`, `help wanted` labels. Avoid issues with `squad:*` labels (team-internal). Check the issue isn't already assigned. +- "My fork is out of date" — step-by-step rebase instructions (already in CONTRIBUTING.md, but worth a FAQ entry pointing to it) +- "What's the `.squad/` directory?" — link to the explainer section + +--- + +#### 2f. "Contributing" Section Enhancement in README.md + +**What:** Expand the README's contributor section beyond just linking to CONTRIBUTING.md. Add a quick "Ways to Contribute" section. + +**Who it helps:** Discovery — README is the first file most people read. + +**Effort:** ~30 minutes. + +**Proposed content:** +```markdown +## Contributing + +We welcome contributions! Here's how to get involved: + +- 🐛 **Report bugs** — [Open an issue](link) using the bug report template +- 💡 **Suggest features** — [Open an issue](link) using the feature request template +- 🔧 **Fix something** — Look for [`good first issue`](link) or [`help wanted`](link) labels +- 📖 **Improve docs** — Typos, clarity, missing examples — all welcome + +**Before you start coding**, read [CONTRIBUTING.md](CONTRIBUTING.md) for setup, branch strategy, and PR requirements. + +All PRs target the `dev` branch. See [Branch Strategy](CONTRIBUTING.md#branch-strategy) for details. +``` + +--- + +### P3 — Nice to Have + +#### 2g. SECURITY.md Typo Fix + +**What:** Fix "manor" → "manner" (line 19). Minor but noticed during audit. + +**Who it helps:** Professional appearance. + +**Effort:** 30 seconds. + +--- + +## 3. Priority Order + +| Priority | Deliverable | Effort | Maintainer Time Saved | Ship With | +|----------|-------------|--------|----------------------|-----------| +| **P1** | Issue templates (2a) | 2 hrs | High — structured reports reduce back-and-forth | This PR or immediate follow-up | +| **P1** | Good first issue curation (2b) | 1.5 hrs | High — gives contributors a clear on-ramp | Separate task (requires issue audit) | +| **P1** | `.squad/` explainer (2c) | 30 min | Medium — prevents common PR mistake | This PR or immediate follow-up | +| **P2** | CODE_OF_CONDUCT.md (2d) | 30 min | Low direct, high trust signal | This PR or immediate follow-up | +| **P2** | Contributor FAQ (2e) | 1 hr | High — answers repeated questions | This PR or immediate follow-up | +| **P2** | README contributing section (2f) | 30 min | Medium — improves discovery | This PR or immediate follow-up | +| **P3** | SECURITY.md typo (2g) | 30 sec | None | Any PR | + +**Recommended execution:** Items 2a + 2c + 2e + 2f + 2g can be shipped in a single PR (estimated 4 hours total). Item 2b (good first issue curation) is a separate task requiring issue-by-issue audit. Item 2d (CODE_OF_CONDUCT) can ship standalone. + +--- + +## 4. Success Criteria + +After these changes ship: + +1. **A new contributor can go from "I found this repo" to "my PR is ready for review" using only the docs** — no DMs, no repeated questions to Brady/Tamir. +2. **Every bug report includes version, OS, and reproduction steps** — enforced by issue template required fields. +3. **Contributors know what to work on** — `good first issue` and `status:contributor-invited` labels are actively maintained with 5+ open issues at all times. +4. **No PRs include `.squad/` changes** — explainer section makes the boundary clear. +5. **Brady and Tamir can respond to contributor questions with links** — "See [FAQ: Which branch do I target?](CONTRIBUTING.md#faq)" instead of writing the answer each time. + +--- + +## 5. What This Does NOT Cover + +- **Automated PR checks for `.squad/` changes** — that's a CI gate (separate issue) +- **Contributor license agreement (CLA)** — Squad uses MIT, no CLA needed currently +- **Discord/Slack community** — out of scope, GitHub Discussions is the channel +- **Docs site contributor guide** — the Astro docs site (`docs/`) has its own needs; this proposal focuses on repo-level contributor docs + +--- + +## 6. Open Questions for Team + +1. **Who should be the CODE_OF_CONDUCT enforcement contact?** Brady as repo owner is the default, but should we list a backup? +2. **Should we add a `.github/FUNDING.yml`?** Not strictly contributor guidance but often expected in OSS. +3. **Should `status:contributor-invited` be renamed to `help wanted`?** GitHub surfaces `help wanted` specially in search and explore. We have both labels — should we consolidate? +4. **PR #639 pattern prevention:** Should we add a "Do Not Contribute" label or a "being removed" label for features that are being deprecated? This would prevent contributors from wasting effort on doomed features. From bdfa21aa51d401575bb3f72116e9dca22b4e21ff Mon Sep 17 00:00:00 2001 From: Dina Berry Date: Wed, 1 Apr 2026 18:57:41 -0700 Subject: [PATCH 3/9] fix(nap): account for separator newlines in decision archival budget Fixes off-by-N error in nap command's decision archival where newline separators between entries weren't counted in the byte budget, causing archives to exceed the target size. Closes #123 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/fix-nap-archival-budget.md | 12 ++++++++++++ CHANGELOG.md | 3 +++ packages/squad-cli/src/cli/core/nap.ts | 13 +++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-nap-archival-budget.md diff --git a/.changeset/fix-nap-archival-budget.md b/.changeset/fix-nap-archival-budget.md new file mode 100644 index 000000000..95461886a --- /dev/null +++ b/.changeset/fix-nap-archival-budget.md @@ -0,0 +1,12 @@ +--- +"@bradygaster/squad-cli": patch +--- + +fix(nap): account for separator newlines in decision archival budget + +The budget calculation in archiveDecisions() did not account for the newline +separators added during content reassembly. This caused the final recentContent +to exceed DECISION_THRESHOLD even after archival. Fix adds reassemblyOverhead +and per-entry separator bytes to the budget calculation. + +Closes #123 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dab04371..7d9972765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fixed +- **Nap archival budget** (#123) — account for separator newlines in decision archival budget calculation + ### Added — Full Work Monitor for squad watch (#708) - `--execute` flag spawns Copilot sessions to work on actionable issues autonomously - Multi-platform support — auto-detects GitHub vs Azure DevOps from git remote URL diff --git a/packages/squad-cli/src/cli/core/nap.ts b/packages/squad-cli/src/cli/core/nap.ts index 8f26c43f7..95775aa6b 100644 --- a/packages/squad-cli/src/cli/core/nap.ts +++ b/packages/squad-cli/src/cli/core/nap.ts @@ -386,17 +386,22 @@ function archiveDecisions(squadDir: string, dryRun: boolean): NapAction | null { dated.sort((a, b) => a.daysAgo! - b.daysAgo!); // Keep the most recent dated entries that fit under the threshold - // along with all undated entries and the header + // along with all undated entries and the header. + // Account for separator newlines added during reassembly: + // recentContent = header + '\n' + entries.join('\n') + '\n' + // Each entry contributes +1 byte for its join separator (overestimates + // by 1 byte for the last entry, which is a safe margin). const headerEnd = entries.length > 0 ? entries[0]!.start : lines.length; const headerSize = Buffer.byteLength(lines.slice(0, headerEnd).join('\n'), 'utf8'); + const reassemblyOverhead = 2; // '\n' after header + trailing '\n' const undatedSize = undated.reduce( - (sum, e) => sum + Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8'), 0, + (sum, e) => sum + Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8') + 1, 0, ); - let budget = DECISION_THRESHOLD - headerSize - undatedSize; + let budget = DECISION_THRESHOLD - headerSize - reassemblyOverhead - undatedSize; const keptDated: typeof entries = []; for (const e of dated) { - const entrySize = Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8'); + const entrySize = Buffer.byteLength(lines.slice(e.start, e.end).join('\n'), 'utf8') + 1; if (budget >= entrySize) { budget -= entrySize; keptDated.push(e); From c2d052d000edfebc3ec014bf344d5a7593ab0bd7 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 2 Apr 2026 14:56:49 +0300 Subject: [PATCH 4/9] feat(watch): port ralph-watch.ps1 resilience features (#743) Port battle-tested features from ralph-watch.ps1 into squad-cli watch. All features are org-agnostic, config-driven, and backward compatible. P0 Reliability: - ModelCircuitBreaker: model-level fallback with cooldown + state persistence - Rate limit detection from API headers, predictive circuit opening - Pre-round health check (auth, disk space, branch drift, CB validation) - Post-failure remediation with tiered self-healing (reset CB, re-auth, git pull) P1 Execution Quality: - Issue priority scoring (P0-P3 labels, age, staleness, size, bug bonus) - Machine capability checking (needs:* labels vs local probes) - Stale work reclaim (unassign issues idle >24h) - Budget check (max issues per round) P2 Observability: - Heartbeat file (.squad/ralph-heartbeat.json) written every round - Structured log (.squad/ralph-watch.log) with rotation - Per-repo lockfile with PID, stale detection - Webhook alerts on consecutive failures (--webhook-url, --alert-threshold) CLI flags: --webhook-url, --alert-threshold, --max-budget, --capabilities Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/squad-cli/src/cli-entry.ts | 34 ++++ .../watch/capabilities/budget-check.ts | 52 ++++++ .../watch/capabilities/circuit-breaker.ts | 170 ++++++++++++++++++ .../watch/capabilities/health-check.ts | 151 ++++++++++++++++ .../commands/watch/capabilities/heartbeat.ts | 141 +++++++++++++++ .../cli/commands/watch/capabilities/index.ts | 12 ++ .../commands/watch/capabilities/lockfile.ts | 123 +++++++++++++ .../capabilities/machine-capabilities.ts | 112 ++++++++++++ .../watch/capabilities/post-failure.ts | 74 ++++++++ .../commands/watch/capabilities/priority.ts | 140 +++++++++++++++ .../watch/capabilities/stale-reclaim.ts | 99 ++++++++++ .../watch/capabilities/webhook-alerts.ts | 122 +++++++++++++ .../src/cli/commands/watch/config.ts | 37 +++- .../squad-cli/src/cli/commands/watch/index.ts | 68 ++++++- 14 files changed, 1332 insertions(+), 3 deletions(-) create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/budget-check.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/circuit-breaker.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/health-check.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/heartbeat.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/lockfile.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/machine-capabilities.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/post-failure.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/priority.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/stale-reclaim.ts create mode 100644 packages/squad-cli/src/cli/commands/watch/capabilities/webhook-alerts.ts diff --git a/packages/squad-cli/src/cli-entry.ts b/packages/squad-cli/src/cli-entry.ts index c6101a71c..c3003c878 100644 --- a/packages/squad-cli/src/cli-entry.ts +++ b/packages/squad-cli/src/cli-entry.ts @@ -176,6 +176,15 @@ async function main(): Promise { console.log(` --wave-dispatch wave-based parallel sub-task dispatch`); console.log(` --retro enforce retrospective checks`); console.log(` --decision-hygiene auto-merge decision inbox`); + console.log(` --health-check pre-round watchdog (auth, disk, branch)`); + console.log(` --stale-reclaim reclaim stale assigned issues (>24h)`); + console.log(` --heartbeat write heartbeat JSON + structured log`); + console.log(` --webhook-alerts POST to webhook on consecutive failures`); + console.log(` Resilience flags (#743):`); + console.log(` --webhook-url webhook for failure alerts`); + console.log(` --alert-threshold failures before alert (default 3)`); + console.log(` --max-budget max issues per round (default 5)`); + console.log(` --capabilities machine caps (e.g., gpu,docker)`); console.log(` Disable: --no- overrides config.json`); console.log(` ${BOLD}loop${RESET} Continuous work loop (Ralph mode)`); console.log(` Usage: loop [--filter