Skip to content

feat(watch): next-gen watch with execute redesign, robustness, and PS1 parity#830

Merged
tamirdresher merged 51 commits intodevfrom
squad/watch-next-gen
Apr 4, 2026
Merged

feat(watch): next-gen watch with execute redesign, robustness, and PS1 parity#830
tamirdresher merged 51 commits intodevfrom
squad/watch-next-gen

Conversation

@tamirdresher
Copy link
Copy Markdown
Collaborator

Squad Watch — Next Generation

Combines 6 PRs into one cohesive watch feature set. This brings the CLI watch to near-parity with the PS1 ralph-watch reference implementation.

What's included

Execute redesign (was PR #776)

  • Issue selection delegated to agent (no pre-filtering in TS)
  • Rich prompt: Task/WHY/Success/Escalation + ralph-instructions.md reference
  • Prompt written to temp file via -p flag (matches PS1 pattern)
  • Fleet dispatch + machine capabilities plugin

Robustness (was PR #782)

  • 4-tier error recovery: reset CB → re-probe auth → git pull → pause 30m
  • Self-pull with stash/restart detection
  • Overnight window (--overnight-start/--overnight-end)
  • Sentinel stop file (.squad/ralph-stop)
  • Generic auth via --auth-user + adapter.ensureAuth(preferredUser)
  • Verbose flag

Capabilities (was PRs #794, #805)

  • Cleanup capability (prune scratch/logs/stale files)
  • Notify-level capability + board context

Health (was PR #809)

  • --health flag shows PID, uptime, auth, capabilities
  • PID file in os.tmpdir with repo-path hash

State backends (was PR #810)

  • --state-backend flag: git-notes, orphan-branch, worktree
  • StateBackend interface + GitNotesBackend + OrphanBranchBackend

Design decisions

  • Agent command is configurable: --agent-cmd + --copilot-flags (no hardcoded 'agency')
  • Auth user is configurable: --auth-user (no hardcoded usernames)
  • All platform-specific code in adapter (GitHub + ADO)
  • Cross-platform: execFileSync, no PS1/bash deps

Closes #775 #781 #792 #793 #804 #807 #808

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Copilot and others added 30 commits April 3, 2026 21:18
…775)

Adds --dispatch-mode flag to squad watch --execute with three modes:
- task (default): existing Promise.all behavior, unchanged
- fleet: all issues dispatched via single /fleet prompt, true parallelism
- hybrid: read-heavy issues via /fleet, write-heavy via task tool + worktrees

Benchmark results (4 real issues):
- Fleet: 116s total (29s/issue avg)
- Sequential: 332s total (83s/issue avg)
- Fleet is 2.9x faster, ~25% cheaper on premium requests

Key finding: /fleet ignores custom agents (.github/agents/) and spawns
generic explore agents. This makes fleet ideal for read-only analysis
(triage, research, reviews) but NOT for charter-driven code changes.

New files:
- fleet-dispatch.ts: FleetDispatchCapability with /fleet prompt builder
- Updated execute.ts: issue classification (read vs write keywords)
- Updated config.ts: DispatchMode type + config field
- Updated cli-entry.ts: --dispatch-mode flag parsing

Closes #775

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove shell:true and stdio from execSync options (fixes overload mismatch)
- Add .changeset/fleet-dispatch-hybrid.md for changelog-gate CI check
- Build verified locally: tsc --noEmit passes clean

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. deferredToFleet count: use executable.length in fleet mode (was read-only count)
2. Warning log when fleet/hybrid defers but fleet-dispatch may not be enabled
3. Command injection fix: execFileSync with args array instead of shell command
4. requires: added 'copilot' alongside 'gh'
5. --dispatch-mode validation: error on invalid values, default to task
6. Unit tests: 11 tests for classifyIssue() covering all keyword categories

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

- New cleanup capability in housekeeping phase
- Clears .squad/.scratch/ temp files
- Archives old orchestration-log and session-log entries (>30d)
- Warns about stale decision inbox files (>7d)
- Configurable: everyNRounds, maxAgeDays
- 12 new tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Default 'important': only print rounds with actual work items
- 'all': old behavior (every round including empty)
- 'none': suppress all round output
- Add machine name + repo name to round headers for attribution
- Configurable via .squad/config.json watch.notifyLevel
- 5 new tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- notification-level.md: full feature doc for --notify-level
- ralph.md: add --notify-level + --cleanup to capabilities table
- 030-v092-whats-coming.md: blog post covering all 10 features in next release

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
captureBoardOutput now defaults to 'all' mode to preserve existing
test behavior. The production default is 'important' but tests need
to verify clear-board message formatting.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CSpell docs-quality CI was failing because 'WCBED' is an unknown
word. Replaced CPC-tamir-WCBED with DEVBOX-01 in the blog post and
notification-level feature doc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds --health flag to squad watch that reports:
- Whether a watch instance is running (PID tracking)
- Uptime, auth account, interval, capabilities
- Auth drift detection (expected vs current gh account)
- Stale PID cleanup for crashed instances

Writes .squad/.watch-pid.json at startup, cleaned on exit/SIGINT.
Auth guard captures active gh user at startup and restores on drift.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds StateBackend interface with three implementations:
- WorktreeBackend: default, reads/writes .squad/ on disk
- GitNotesBackend: stores state in refs/notes/squad
- OrphanBranchBackend: stores state on squad-state orphan branch

Also adds:
- resolveStateBackend() config resolution with fallback
- --state-backend CLI flag for init and watch commands
- stateBackend field in SquadDirConfig and WatchConfig
- Unit tests covering all backends and config resolution

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Moves .squad/.watch-pid.json → {os.tmpdir()}/squad-watch-{hash}.json
to prevent accidental git commits. Uses MD5 hash of repo path for
worktree/multi-clone safety. Cross-platform via os.tmpdir().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add warning log when dispatchMode=fleet/hybrid but fleet-dispatch cap is not enabled
- Add comment documenting --dispatch-mode runtime validation
- Re-export classifyIssue from watch/index barrel
- Add classifyIssue unit tests for dispatch-mode categories
- Pass dispatchMode through to capability synthesized config

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused fs import from watch/index.ts
- Fix SIGINT exit code to 128+2 (130), SIGTERM to 128+15 (143)
- Replace (adapter as any) with proper type narrowing via optional method check
- Handle EPERM in isProcessAlive (process exists but no permission)
- Add startedAt validation guard before computing uptime
- Wire --health flag in cli-entry.ts
- Add doc comment for probeCurrentGhUser stderr handling
- Add EPERM test case for isProcessAlive

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename 'archive' to 'prune' in all log messages and comments
- Use rmSync with recursive to handle subdirectories in .scratch/
- Add Number.isFinite and >0 guards to parseConfig validation
- Use Date.now() consistently for UTC cutoff calculation
- Fix changeset: correct test count from 11 to 12

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add restart message after --self upgrade (stale code warning)
- Fix permission handling: only suggest sudo for npm, not pnpm/yarn
- Warn when --insider used without --self
- Add test verifying selfUpgradeCli is called with correct args
- Export selfUpgradeCli, call it from cli-entry before runUpgrade (exit early)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix shell injection in gitExec: use execFileSync with args array
- Add path traversal validation in WorktreeBackend (reject .. in paths)
- Fix GitNotes list('') to return root entries (empty prefix match)
- Fix prototype pollution: use Object.hasOwn() instead of in operator
- Validate --state-backend value and reject flag-as-value
- Add gitExecContent with trimEnd() for content reads
- Add comment documenting config merge-not-overwrite pattern

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add JSDoc to reportBoard() documenting default notifyLevel='important'
- Include machine/repo context in 'Board is clear' message for all modes
- Add comment noting verbose flag parsing is in PR #782
- Replace console.log direct mutation with vi.spyOn in tests
- Update changeset to clarify attribution is in round headers and clear message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es PS1 design)

The execute capability now passes ALL squad issues to the agent and lets the
agent decide what to work on, matching the PS1 ralph-watch behavior. The prompt
includes Task/WHY/Success/Escalation structure and references
.squad/ralph-instructions.md for full instructions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es PS1 design)

The execute capability now passes ALL squad issues to the agent and lets the
agent decide what to work on, matching the PS1 ralph-watch behavior. The prompt
includes Task/WHY/Success/Escalation structure and references
.squad/ralph-instructions.md for full instructions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4-tier remediation: reset CB → re-probe auth → git pull → pause 30m.
Consecutive failures escalate; successes de-escalate.
Startup auth check now attempts repair before hard-failing.
Sentinel file + overnight window checks at round start.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Self-pull now stashes local changes before pulling and pops after.
Detects when watch source files changed and warns to restart.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Watch now supports --overnight-start/--overnight-end to pause during off-hours
and checks .squad/ralph-stop sentinel file for graceful shutdown.
Config interface extended with overnightStart, overnightEnd, sentinelFile.

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

The PR #776 simplified findExecutableIssues to only filter by squad label
(intentionally minimal), but this dropped two pre-existing filters that
the test suite expects:
  - exclude issues already assigned to a human
  - exclude issues with status:blocked (or similar blocking labels)

These filters match the PS1 ralph-watch pre-filter logic and are the right
behavior — the agent should only receive clearly actionable issues.

Fixes test: CLI: watch execute mode > findExecutableIssues > returns only
issues ready for execution
…ntext.ps1

Built-in auth context switching through the adapter abstraction:
- GitHubAdapter: parses remote URL, checks gh auth, auto-switches if mismatch
- AzureDevOpsAdapter: parses remote URL, configures az devops defaults
- Cross-platform (no PS1/bash deps), non-fatal on failure
- Tiered remediation Tier 2 now uses adapter.ensureAuth() instead of hardcoded user

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

github-actions bot commented Apr 4, 2026

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit c16d988

⚠️ 4 item(s) to address before review

Status Check Details
Single commit 51 commits — consider squashing before review
Not in draft Ready for review
Branch up to date Up to date with dev
Copilot review No Copilot review yet — it may still be processing
Changeset present Changeset file found
Scope clean No .squad/ or docs/proposals/ files
No merge conflicts No merge conflicts
Copilot threads resolved 6 unresolved Copilot thread(s) — fix and resolve before merging
CI passing 14 check(s) still running

This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

🏗️ Architectural Review

⚠️ Architectural review: 2 warning(s).

Severity Category Finding Files
🟡 warning export-surface Package entry point(s) modified with 19 new/changed export(s). New public API surface requires careful review for backward compatibility. packages/squad-sdk/src/index.ts
🟡 warning sweeping-refactor This PR touches 32 files (32 modified/added, 0 deleted). Large PRs are harder to review — consider splitting if possible.

Automated architectural review — informational only.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 4, 2026

🟠 Impact Analysis — PR #830

Risk tier: 🟠 HIGH

📊 Summary

Metric Count
Files changed 32
Files added 14
Files modified 18
Files deleted 0
Modules touched 5
Critical files 4

🎯 Risk Factors

  • 32 files changed (21-50 → HIGH)
  • 5 modules touched (5-8 → HIGH)
  • Critical files touched: packages/squad-cli/package.json, packages/squad-cli/src/cli/commands/watch/capabilities/index.ts, packages/squad-cli/src/cli/commands/watch/index.ts, packages/squad-sdk/src/index.ts

📦 Modules Affected

docs (4 files)
  • docs/features/watch-next-gen.md
  • docs/src/content/blog/030-v092-whats-coming.md
  • docs/src/content/docs/features/notification-level.md
  • docs/src/content/docs/features/ralph.md
root (7 files)
  • .changeset/cleanup-ceremony.md
  • .changeset/git-state-backends.md
  • .changeset/quiet-notifications.md
  • .changeset/watch-health.md
  • .gitignore
  • README.md
  • cspell.json
squad-cli (8 files)
  • packages/squad-cli/package.json
  • packages/squad-cli/src/cli-entry.ts
  • packages/squad-cli/src/cli/commands/watch/capabilities/cleanup.ts
  • packages/squad-cli/src/cli/commands/watch/capabilities/index.ts
  • packages/squad-cli/src/cli/commands/watch/capabilities/self-pull.ts
  • packages/squad-cli/src/cli/commands/watch/config.ts
  • packages/squad-cli/src/cli/commands/watch/health.ts
  • packages/squad-cli/src/cli/commands/watch/index.ts
squad-sdk (6 files)
  • packages/squad-sdk/src/index.ts
  • packages/squad-sdk/src/platform/azure-devops.ts
  • packages/squad-sdk/src/platform/github.ts
  • packages/squad-sdk/src/platform/types.ts
  • packages/squad-sdk/src/resolution.ts
  • packages/squad-sdk/src/state-backend.ts
tests (7 files)
  • test/cli-command-wiring.test.ts
  • test/cli/upgrade.test.ts
  • test/cli/watch-health.test.ts
  • test/ralph-board.test.ts
  • test/state-backend.test.ts
  • test/watch-cleanup.test.ts
  • test/watch-notify-level.test.ts

⚠️ Critical Files

  • packages/squad-cli/package.json
  • packages/squad-cli/src/cli/commands/watch/capabilities/index.ts
  • packages/squad-cli/src/cli/commands/watch/index.ts
  • packages/squad-sdk/src/index.ts

This report is generated automatically for every PR. See #733 for details.

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

This PR delivers a “next-gen” squad watch experience, expanding the watch loop with new execution/dispatch behaviors, robustness features, health reporting, and related SDK support (scratch dir + git-native state backends), plus docs and test coverage.

Changes:

  • Add watch enhancements: notify-level reporting, /fleet dispatch capability, cleanup capability, auth guard, and health check PID tracking.
  • Introduce SDK utilities: scratch directory helpers and git-native state backends (git-notes + orphan branch).
  • Add CLI self-upgrade support and accompanying docs/changesets/tests.

Reviewed changes

Copilot reviewed 37 out of 38 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
test/watch-notify-level.test.ts Adds unit tests for notify-level output suppression/attribution.
test/watch-fleet-dispatch.test.ts Adds unit tests for classifyIssue() read/write categorization.
test/watch-cleanup.test.ts Adds unit tests for the new cleanup capability behavior.
test/state-backend.test.ts Adds unit tests for new SDK state backends + resolver.
test/scratch-dir.test.ts Adds unit tests for scratchDir() / scratchFile() helpers.
test/ralph-board.test.ts Updates existing board-output test helper to pass notifyLevel.
test/cli/watch-health.test.ts Adds tests for watch health check + PID file behaviors.
test/cli/watch-fleet-dispatch.test.ts Adds CLI-level tests for dispatch classification.
test/cli/watch-execute.test.ts Extends execute-mode tests to cover classification and routing expectations.
test/cli/upgrade.test.ts Adds a test for upgrade --self behavior.
packages/squad-sdk/src/state-backend.ts Implements Worktree/GitNotes/OrphanBranch state backends + resolver.
packages/squad-sdk/src/resolution.ts Adds scratchDir() and scratchFile() utilities and stateBackend config field.
packages/squad-sdk/src/platform/types.ts Extends platform adapter interface with optional ensureAuth().
packages/squad-sdk/src/platform/github.ts Implements GitHub ensureAuth() auto-switch behavior.
packages/squad-sdk/src/platform/azure-devops.ts Implements ADO ensureAuth() org default configuration.
packages/squad-sdk/src/index.ts Exports scratch helpers and state backend APIs from SDK barrel.
packages/squad-sdk/src/config/init.ts Ensures .squad/.scratch/ exists and is gitignored on init.
packages/squad-cli/src/cli/core/upgrade.ts Adds selfUpgradeCli() for upgrading the CLI package itself.
packages/squad-cli/src/cli/commands/watch/index.ts Adds notify-level reporting, auth guard, PID file integration, remediation tiers, and re-exports.
packages/squad-cli/src/cli/commands/watch/health.ts Implements --health status output via PID file in OS temp.
packages/squad-cli/src/cli/commands/watch/config.ts Extends watch config with notifyLevel/verbose/dispatch/etc.
packages/squad-cli/src/cli/commands/watch/capabilities/self-pull.ts Enhances self-pull to stash/pull/pop + restart warning.
packages/squad-cli/src/cli/commands/watch/capabilities/index.ts Registers new fleet-dispatch and cleanup capabilities.
packages/squad-cli/src/cli/commands/watch/capabilities/fleet-dispatch.ts Adds /fleet batching dispatch capability.
packages/squad-cli/src/cli/commands/watch/capabilities/execute.ts Redesigns execute behavior to build a richer prompt and classify issues.
packages/squad-cli/src/cli/commands/watch/capabilities/cleanup.ts Adds housekeeping cleanup capability for scratch/log/inbox.
packages/squad-cli/src/cli-entry.ts Adds --health, --self/--insider, and begins parsing new watch flags.
packages/squad-cli/package.json Exports the new watch health module entrypoint.
docs/src/content/docs/features/ralph.md Updates Ralph/watch docs to mention notify-level and cleanup.
docs/src/content/docs/features/notification-level.md Adds a dedicated feature doc page for notify-level.
docs/src/content/blog/030-v092-whats-coming.md Adds a draft blog post highlighting the upcoming features.
.gitignore Minor formatting change (blank line).
.changeset/watch-health.md Changeset entry for watch health feature.
.changeset/scratch-dir-utility.md Changeset entry for scratch dir utility.
.changeset/quiet-notifications.md Changeset entry for notify-level feature.
.changeset/git-state-backends.md Changeset entry for git-native state backends.
.changeset/fleet-dispatch-hybrid.md Changeset entry for fleet/hybrid dispatch.
.changeset/cleanup-ceremony.md Changeset entry for cleanup capability.

Comment on lines +52 to +57
*/
export function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

isProcessAlive() treats any process.kill(pid, 0) error as “dead”, but on some platforms (or when lacking permissions) kill throws EPERM even though the process exists. This breaks the intended behavior (and the added test). Handle EPERM as “alive” and only return false for ESRCH (and other definitive non-existence cases).

Suggested change
*/
export function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch {
* `EPERM` means the process exists but cannot be signaled by this user.
*/
export function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch (error) {
const code = typeof error === 'object' && error !== null && 'code' in error
? (error as { code?: string }).code
: undefined;
if (code === 'EPERM') {
return true;
}
if (code === 'ESRCH') {
return false;
}

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +89
function probeCurrentGhUser(): string | undefined {
try {
const result = execFileSync('gh', ['auth', 'status', '--active'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
const match = result.match(/account\s+(\S+)/);
return match?.[1];
} catch (e) {
const stderr = (e as { stderr?: string }).stderr ?? '';
const match = stderr.match(/account\s+(\S+)/);
return match?.[1];
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

probeCurrentGhUser() (and the similar helper in watch/index.ts) only inspects stdout from gh auth status --active, but execFileSync() does not provide stderr on success. Since gh auth status frequently writes its status text to stderr even with exit code 0, this can return undefined even when authenticated. Consider using spawnSync/execFileSync via spawnSync result to parse both stdout+stderr on success as well as failure.

Copilot uses AI. Check for mistakes.
Comment on lines 418 to 479
@@ -377,6 +443,12 @@ async function main(): Promise<void> {
if (args.includes(`--no-${cap.name}`)) capabilities[cap.name] = false;
}

// --state-backend flag for watch command
const watchStateBackendIdx = args.indexOf('--state-backend');
const rawWatchStateBackend = (watchStateBackendIdx !== -1 && args[watchStateBackendIdx + 1])
? args[watchStateBackendIdx + 1] as string
: undefined;

// Legacy flag compat: --board-project sets board sub-option
const boardProjectIdx = args.indexOf('--board-project');
if (boardProjectIdx !== -1 && args[boardProjectIdx + 1]) {
@@ -386,6 +458,14 @@ async function main(): Promise<void> {
: { projectNumber: parseInt(args[boardProjectIdx + 1]!, 10) };
}

// Notify level: --notify-level all|important|none (default: important)
const notifyLevelIdx = args.indexOf('--notify-level');
const notifyLevelArg = (notifyLevelIdx !== -1 && args[notifyLevelIdx + 1]) ? args[notifyLevelIdx + 1] : undefined;
const notifyLevel = (notifyLevelArg === 'all' || notifyLevelArg === 'important' || notifyLevelArg === 'none')
? notifyLevelArg : undefined;

// verbose flag parsing is in PR #782 (--verbose)

// Load config: .squad/config.json merged with CLI overrides
const config = loadWatchConfig(process.cwd(), {
interval,
@@ -394,6 +474,7 @@ async function main(): Promise<void> {
timeout,
copilotFlags,
agentCmd,
stateBackend: rawWatchStateBackend as any,
capabilities: Object.keys(capabilities).length > 0 ? capabilities : undefined,
});
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

Several new watch CLI flags are parsed (--notify-level, --overnight-start/--overnight-end, --sentinel-file, --auth-user), but the parsed values are never included in the cliOverrides passed to loadWatchConfig(). As a result, the flags have no effect at runtime. Also, --dispatch-mode is documented in the PR but is not parsed here at all, so config.dispatchMode can only be set via config file.

Copilot uses AI. Check for mistakes.
Comment on lines +300 to +324
// --state-backend: write stateBackend into .squad/config.json on init
const stateBackendIdx = args.indexOf('--state-backend');
const stateBackendVal = (stateBackendIdx !== -1 && args[stateBackendIdx + 1])
? args[stateBackendIdx + 1]
: undefined;

// Global init: suppress workflows (no GitHub CI in ~/.config/squad/) and bootstrap personal squad
runInit(dest, { includeWorkflows: !noWorkflows && !hasGlobal, sdk, roles, isGlobal: hasGlobal }).catch(err => {
runInit(dest, { includeWorkflows: !noWorkflows && !hasGlobal, sdk, roles, isGlobal: hasGlobal }).then(async () => {
if (stateBackendVal) {
const { join } = await import('node:path');
const { existsSync, readFileSync, writeFileSync, mkdirSync } = await import('node:fs');
const squadDir = join(dest, '.squad');
if (!existsSync(squadDir)) mkdirSync(squadDir, { recursive: true });
const configPath = join(squadDir, 'config.json');
// Read existing config first, then merge (avoids overwriting unrelated keys)
let config: Record<string, unknown> = {};
try {
if (existsSync(configPath)) {
config = JSON.parse(readFileSync(configPath, 'utf-8'));
}
} catch { /* fresh config */ }
config['stateBackend'] = stateBackendVal;
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
console.log(`✓ State backend set to '${stateBackendVal}' in .squad/config.json`);
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

--state-backend is written verbatim into .squad/config.json without validation. This allows invalid values that will later be ignored/fall back, but it’s better UX to validate upfront (worktree|external|git-notes|orphan) and fail fast with a clear message rather than persisting a bad config.

Copilot uses AI. Check for mistakes.
Comment on lines +636 to +666
export async function selfUpgradeCli(options: { insider?: boolean; force?: boolean } = {}): Promise<void> {
const { execSync } = await import('node:child_process');
const tag = options.insider ? 'insider' : 'latest';
const pkg = `@bradygaster/squad-cli@${tag}`;
const pm = detectPackageManager();

let cmd: string;
switch (pm) {
case 'pnpm':
cmd = `pnpm add -g ${pkg}`;
break;
case 'yarn':
cmd = `yarn global add ${pkg}`;
break;
default:
cmd = `npm install -g ${pkg}`;
break;
}

info(`Self-upgrading via ${pm}: ${cmd}`);

try {
execSync(cmd, { stdio: 'inherit' });
} catch {
// Only suggest sudo for npm — pnpm/yarn rarely need it
if (pm === 'npm') {
warn(`Permission denied. Try: sudo ${cmd}`);
} else {
warn(`Upgrade failed. Check ${pm} permissions or try running manually: ${cmd}`);
}
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

selfUpgradeCli() builds a single shell command string and runs it via execSync(cmd), which (1) routes through the shell, (2) makes it harder to test/mocks (the added test spies on execFileSync, so it won’t observe this), and (3) ignores the force option entirely. Prefer execFileSync() with an args array (npm/pnpm/yarn + args), and wire options.force into the appropriate flag (e.g. --force for npm) so behavior matches the function signature and tests can reliably assert the invocation.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,152 @@
/**
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

File appears to start with a UTF-8 BOM / zero-width character before /** (shown as /**). This can cause noisy diffs and occasional tooling issues (linters, shebang parsing, etc.). Please remove the BOM so the file begins with a plain /**.

Suggested change
/**
/**

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,12 @@
---
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

This changeset file also appears to start with a UTF-8 BOM (---). Please remove the BOM so frontmatter parsing remains consistent across tools and platforms.

Suggested change
---
---

Copilot uses AI. Check for mistakes.

Adds `squad watch --health` to display the status of a running watch
instance: PID, uptime, auth account, capabilities, and auth drift
detection. Writes `.squad/.watch-pid.json` at startup for instance
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The changeset text says the PID file is written to .squad/.watch-pid.json, but the implementation writes to an OS temp file (os.tmpdir()/squad-watch-{hash}.json) via getPidPath(). Please update the changeset (and any docs) to reflect the actual location, or change the implementation to match the documented .squad/ path.

Suggested change
detection. Writes `.squad/.watch-pid.json` at startup for instance
detection. Writes a watch PID file in the OS temp directory
(`os.tmpdir()/squad-watch-{hash}.json`) at startup for instance

Copilot uses AI. Check for mistakes.
// 3. Switch
try {
execFileSync('gh', ['auth', 'switch', '--user', targetUser], EXEC_OPTS);
console.log(`✅ Auth context switched to ${targetUser}`);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

ensureAuth() logs directly to stdout (console.log("✅ Auth context switched…")). Since this is in the SDK adapter layer, it introduces an unconditional user-facing side effect from a library call (and will also trigger the repo’s no-console eslint warning for console.log). Prefer returning a status (e.g., switched/targetUser) and letting the CLI decide when/how to display it (or only log under a passed-in verbose/logger option).

Suggested change
console.log(`✅ Auth context switched to ${targetUser}`);

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +67
export function getActiveGhUser(): string | undefined {
try {
const result = execFileSync('gh', ['auth', 'status', '--active'], {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
// gh auth status outputs: "Logged in to github.com account XXXXX ..."
// Capture stderr too — gh writes status info to both streams
const match = result.match(/account\s+(\S+)/);
return match?.[1];
} catch (e) {
// gh auth status exits non-zero when not logged in, but still
// writes account info to stderr — try to parse it
const stderr = (e as { stderr?: string }).stderr ?? '';
const match = stderr.match(/account\s+(\S+)/);
return match?.[1];
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

getActiveGhUser() claims to “capture stderr too”, but execFileSync() only returns stdout on success; stderr is only accessible on thrown errors. Since gh auth status often writes its status to stderr even with exit code 0, this function can return undefined even when logged in, weakening auth-drift detection and PID info. Consider using spawnSync (so you can inspect both stdout and stderr on success) or a gh api user -q .login style query.

Copilot uses AI. Check for mistakes.
Copilot and others added 3 commits April 4, 2026 22:35
- Print 'Starting round N...' and 'Next poll at HH:MM' for user feedback
- Print 'Running first check now...' before first round
- --log-file mirrors all output to file with ISO timestamps (ANSI stripped)
- Log file supports append mode for multi-session diagnostics

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
execFile buffers all output until the process exits, making the terminal
appear frozen during agency runs. spawn with stdio:'inherit' streams output
in real-time, matching the PS1's Start-Process -NoNewWindow behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Owner

@bradygaster bradygaster left a comment

Choose a reason for hiding this comment

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

once copilot's suggestions [that you agree with] are done i'm good here. thanks!


Add cleanup watch capability for stale file housekeeping (#791)

- New `cleanup` capability in the `housekeeping` phase
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

i think we should make sure to differentiate between the cleanup and nap commands but, maybe i just need to RTFM. :)

Copilot and others added 7 commits April 4, 2026 23:51
- Fix isProcessAlive EPERM handling in watch/health.ts
- Fix watch-cleanup test assertions (pruned vs archived)
- Add vi import to upgrade.test.ts
- Fix security review (git add . in blog prose)
- Revert CHANGELOG.md (write-protected)
- Add DEVBOX/myaccount to cspell dictionary

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep both our selfUpgradeCli test and dev's casting tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tamirdresher tamirdresher merged commit 7f9a878 into dev Apr 4, 2026
17 checks passed
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.

[Watch] Add /fleet parallel dispatch mode for squad watch --execute

3 participants