@bradygaster/squad-cli 0.9.2-insider.6 → 0.9.4-insider.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +27 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/externalize.d.ts +27 -0
- package/dist/cli/commands/externalize.d.ts.map +1 -0
- package/dist/cli/commands/externalize.js +207 -0
- package/dist/cli/commands/externalize.js.map +1 -0
- package/dist/cli/commands/loop.d.ts +56 -0
- package/dist/cli/commands/loop.d.ts.map +1 -0
- package/dist/cli/commands/loop.js +352 -0
- package/dist/cli/commands/loop.js.map +1 -0
- package/dist/cli/commands/migrate.js +2 -2
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/rc.js +2 -2
- package/dist/cli/commands/rc.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/cleanup.d.ts +18 -0
- package/dist/cli/commands/watch/capabilities/cleanup.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/cleanup.js +135 -0
- package/dist/cli/commands/watch/capabilities/cleanup.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/execute.d.ts +15 -3
- package/dist/cli/commands/watch/capabilities/execute.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/execute.js +106 -86
- package/dist/cli/commands/watch/capabilities/execute.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.d.ts +20 -0
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.js +144 -0
- package/dist/cli/commands/watch/capabilities/fleet-dispatch.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/index.d.ts +0 -2
- package/dist/cli/commands/watch/capabilities/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/index.js +4 -12
- package/dist/cli/commands/watch/capabilities/index.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/self-pull.d.ts +4 -1
- package/dist/cli/commands/watch/capabilities/self-pull.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/self-pull.js +53 -5
- package/dist/cli/commands/watch/capabilities/self-pull.js.map +1 -1
- package/dist/cli/commands/watch/config.d.ts +10 -15
- package/dist/cli/commands/watch/config.d.ts.map +1 -1
- package/dist/cli/commands/watch/config.js +14 -47
- package/dist/cli/commands/watch/config.js.map +1 -1
- package/dist/cli/commands/watch/health.d.ts +41 -0
- package/dist/cli/commands/watch/health.d.ts.map +1 -0
- package/dist/cli/commands/watch/health.js +141 -0
- package/dist/cli/commands/watch/health.js.map +1 -0
- package/dist/cli/commands/watch/index.d.ts +9 -16
- package/dist/cli/commands/watch/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/index.js +117 -63
- package/dist/cli/commands/watch/index.js.map +1 -1
- package/dist/cli/commands/watch/types.d.ts +2 -0
- package/dist/cli/commands/watch/types.d.ts.map +1 -1
- package/dist/cli/commands/watch/verbose.d.ts +12 -0
- package/dist/cli/commands/watch/verbose.d.ts.map +1 -0
- package/dist/cli/commands/watch/verbose.js +28 -0
- package/dist/cli/commands/watch/verbose.js.map +1 -0
- package/dist/cli/core/detect-squad-dir.d.ts.map +1 -1
- package/dist/cli/core/detect-squad-dir.js +9 -12
- package/dist/cli/core/detect-squad-dir.js.map +1 -1
- package/dist/cli/core/email-scrub.js +2 -2
- package/dist/cli/core/email-scrub.js.map +1 -1
- package/dist/cli/core/init.d.ts.map +1 -1
- package/dist/cli/core/init.js +4 -1
- package/dist/cli/core/init.js.map +1 -1
- package/dist/cli/core/migrations.js +1 -1
- package/dist/cli/core/migrations.js.map +1 -1
- package/dist/cli/core/nap.js +2 -2
- package/dist/cli/core/nap.js.map +1 -1
- package/dist/cli/core/project-type.js +1 -1
- package/dist/cli/core/project-type.js.map +1 -1
- package/dist/cli/core/templates.d.ts.map +1 -1
- package/dist/cli/core/templates.js +48 -0
- package/dist/cli/core/templates.js.map +1 -1
- package/dist/cli/core/upgrade.d.ts +20 -0
- package/dist/cli/core/upgrade.d.ts.map +1 -1
- package/dist/cli/core/upgrade.js +141 -15
- package/dist/cli/core/upgrade.js.map +1 -1
- package/dist/cli/shell/session-store.js +2 -2
- package/dist/cli/shell/session-store.js.map +1 -1
- package/dist/cli-entry.js +201 -89
- package/dist/cli-entry.js.map +1 -1
- package/package.json +6 -2
- package/templates/casting-reference.md +104 -104
- package/templates/ceremonies.md +28 -0
- package/templates/fact-checker-charter.md +83 -0
- package/templates/issue-lifecycle.md +2 -1
- package/templates/loop.md +46 -0
- package/templates/ralph-triage.js +3 -1
- package/templates/scribe-charter.md +20 -1
- package/templates/skills/external-comms/SKILL.md +329 -329
- package/templates/skills/gh-auth-isolation/SKILL.md +183 -183
- package/templates/skills/humanizer/SKILL.md +105 -105
- package/templates/skills/pr-review-response/SKILL.md +268 -0
- package/templates/skills/pr-screenshots/SKILL.md +149 -149
- package/templates/skills/versioning-policy/SKILL.md +119 -0
- package/templates/squad.agent.md.template +9 -7
- package/templates/workflows/squad-triage.yml +4 -2
- package/templates/workflows/sync-squad-labels.yml +3 -1
- package/dist/cli/commands/watch/capabilities/budget-check.d.ts +0 -29
- package/dist/cli/commands/watch/capabilities/budget-check.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/budget-check.js +0 -38
- package/dist/cli/commands/watch/capabilities/budget-check.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts +0 -52
- package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/circuit-breaker.js +0 -152
- package/dist/cli/commands/watch/capabilities/circuit-breaker.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/health-check.d.ts +0 -29
- package/dist/cli/commands/watch/capabilities/health-check.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/health-check.js +0 -139
- package/dist/cli/commands/watch/capabilities/health-check.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/heartbeat.d.ts +0 -48
- package/dist/cli/commands/watch/capabilities/heartbeat.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/heartbeat.js +0 -115
- package/dist/cli/commands/watch/capabilities/heartbeat.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/lockfile.d.ts +0 -30
- package/dist/cli/commands/watch/capabilities/lockfile.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/lockfile.js +0 -100
- package/dist/cli/commands/watch/capabilities/lockfile.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts +0 -30
- package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/machine-capabilities.js +0 -103
- package/dist/cli/commands/watch/capabilities/machine-capabilities.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/post-failure.d.ts +0 -19
- package/dist/cli/commands/watch/capabilities/post-failure.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/post-failure.js +0 -58
- package/dist/cli/commands/watch/capabilities/post-failure.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/priority.d.ts +0 -59
- package/dist/cli/commands/watch/capabilities/priority.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/priority.js +0 -101
- package/dist/cli/commands/watch/capabilities/priority.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/rate-pool.d.ts +0 -67
- package/dist/cli/commands/watch/capabilities/rate-pool.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/rate-pool.js +0 -187
- package/dist/cli/commands/watch/capabilities/rate-pool.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts +0 -23
- package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/stale-reclaim.js +0 -87
- package/dist/cli/commands/watch/capabilities/stale-reclaim.js.map +0 -1
- package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts +0 -29
- package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts.map +0 -1
- package/dist/cli/commands/watch/capabilities/webhook-alerts.js +0 -114
- package/dist/cli/commands/watch/capabilities/webhook-alerts.js.map +0 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "versioning-policy"
|
|
3
|
+
description: "Semver versioning rules for Squad SDK and CLI — prevents prerelease version incidents"
|
|
4
|
+
domain: "release, versioning, npm, CI"
|
|
5
|
+
confidence: "medium"
|
|
6
|
+
source: "earned (PR #640 workspace resolution incident, PR #116 prerelease leak, CI gate implementation)"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Squad is a monorepo with two publishable npm packages (`@bradygaster/squad-sdk` and `@bradygaster/squad-cli`) managed via npm workspaces. Version mismatches and prerelease leaks have caused production incidents — most notably PR #640, where a `-build.N` prerelease version silently broke workspace dependency resolution.
|
|
12
|
+
|
|
13
|
+
This skill codifies the versioning rules every agent must follow.
|
|
14
|
+
|
|
15
|
+
## 1. Version Format
|
|
16
|
+
|
|
17
|
+
All packages use **strict semver**: `MAJOR.MINOR.PATCH`
|
|
18
|
+
|
|
19
|
+
- ✅ `0.9.1`, `1.0.0`, `0.10.0`
|
|
20
|
+
- ❌ `0.9.1-build.4`, `0.9.1-preview.1`, `0.8.6.1-preview`
|
|
21
|
+
|
|
22
|
+
No prerelease suffixes on `dev` or `main` branches — ever.
|
|
23
|
+
|
|
24
|
+
## 2. Prerelease Versions Are Ephemeral
|
|
25
|
+
|
|
26
|
+
The `scripts/bump-build.mjs` script creates `-build.N` versions (e.g., `0.9.1-build.4`) for **local development testing only**.
|
|
27
|
+
|
|
28
|
+
Rules:
|
|
29
|
+
- `-build.N` versions are created automatically during local `npm run build`
|
|
30
|
+
- They are **never committed** to `dev` or `main`
|
|
31
|
+
- The script skips itself in CI (`CI=true` or `SKIP_BUILD_BUMP=1`)
|
|
32
|
+
- If you see a `-build.N` version in a PR diff, it is a bug — reject the PR
|
|
33
|
+
|
|
34
|
+
## 3. SDK and CLI Version Sync
|
|
35
|
+
|
|
36
|
+
Both `@bradygaster/squad-sdk` and `@bradygaster/squad-cli` **MUST have the same version** at all times. The root `package.json` version must also match.
|
|
37
|
+
|
|
38
|
+
`bump-build.mjs` enforces this by updating all three `package.json` files in lockstep (root + `packages/squad-sdk` + `packages/squad-cli`).
|
|
39
|
+
|
|
40
|
+
If versions diverge, workspace resolution silently breaks (see §4).
|
|
41
|
+
|
|
42
|
+
## 4. npm Workspace Semver Footgun
|
|
43
|
+
|
|
44
|
+
The CLI depends on the SDK via a workspace dependency with a semver range:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
"@bradygaster/squad-sdk": ">=0.9.0"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Critical:** Per the semver specification, `>=0.9.0` does **NOT** match `0.9.1-build.4`.
|
|
51
|
+
|
|
52
|
+
Semver prerelease versions (anything with a `-` suffix) are only matched by ranges that explicitly reference the same `MAJOR.MINOR.PATCH` base with a prerelease comparator. A bare `>=0.9.0` range skips all prerelease versions.
|
|
53
|
+
|
|
54
|
+
**What happens:** When the local SDK has version `0.9.1-build.4`, npm's workspace resolution fails to match the `>=0.9.0` range. npm then **silently installs a stale published version** from the npm registry instead of using the local workspace link. The build succeeds but runs against old SDK code.
|
|
55
|
+
|
|
56
|
+
This is the root cause of the **PR #640 incident**, where workspace packages appeared linked but were actually running against stale registry versions.
|
|
57
|
+
|
|
58
|
+
## 5. Who Bumps Versions
|
|
59
|
+
|
|
60
|
+
**Surgeon (Release Manager) owns all version bumps.**
|
|
61
|
+
|
|
62
|
+
| Agent | May modify `version` in package.json? |
|
|
63
|
+
|-------|---------------------------------------|
|
|
64
|
+
| Surgeon | ✅ Yes — sole owner of version bumps |
|
|
65
|
+
| Any other agent | ❌ No — unless explicitly fixing a prerelease leak |
|
|
66
|
+
|
|
67
|
+
If you discover a prerelease version committed to `dev` or `main`, you may fix it (revert to the clean release version) without Surgeon's approval. This is a safety escape hatch, not a license to manage versions.
|
|
68
|
+
|
|
69
|
+
## 6. Version Bump Lifecycle
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
┌─────────────────────────────────────────────────────────┐
|
|
73
|
+
│ Development phase │
|
|
74
|
+
│ Versions stay at current release: 0.9.1 │
|
|
75
|
+
│ bump-build.mjs creates -build.N locally (not committed)│
|
|
76
|
+
├─────────────────────────────────────────────────────────┤
|
|
77
|
+
│ Pre-release testing │
|
|
78
|
+
│ bump-build.mjs → 0.9.1-build.1, -build.2, ... │
|
|
79
|
+
│ Local only. Never committed. Never pushed. │
|
|
80
|
+
├─────────────────────────────────────────────────────────┤
|
|
81
|
+
│ Release │
|
|
82
|
+
│ Surgeon bumps to next version (e.g., 0.9.2 or 0.10.0) │
|
|
83
|
+
│ Tags, publishes to npm registry │
|
|
84
|
+
├─────────────────────────────────────────────────────────┤
|
|
85
|
+
│ Post-release │
|
|
86
|
+
│ Versions stay at the new release version (e.g., 0.9.2) │
|
|
87
|
+
│ Development continues on clean version │
|
|
88
|
+
└─────────────────────────────────────────────────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 7. CI Enforcement
|
|
92
|
+
|
|
93
|
+
The **`prerelease-version-guard`** CI gate blocks any PR to `dev` or `main` that contains prerelease version strings in `package.json` files.
|
|
94
|
+
|
|
95
|
+
- The gate scans all three `package.json` files for `-` in the version field
|
|
96
|
+
- PRs with prerelease versions **cannot merge** until the version is cleaned
|
|
97
|
+
- The `skip-version-check` label bypasses the gate — use **only** for the bump-build script's own PR (if applicable), and only with Surgeon's approval
|
|
98
|
+
|
|
99
|
+
## 8. Incident Reference — PR #640
|
|
100
|
+
|
|
101
|
+
**PR #640** is the cautionary tale for this entire policy.
|
|
102
|
+
|
|
103
|
+
**What happened:** Prerelease versions (`0.9.1-build.4`) were committed to a branch. The workspace dependency `>=0.9.0` failed to match the prerelease version per semver spec. npm silently installed a stale published SDK from the registry instead of linking the local workspace copy. Four PRs (#637–#640) attempted iterative patches before the root cause was identified.
|
|
104
|
+
|
|
105
|
+
**Root cause:** No versioning policy existed. Agents didn't know that prerelease versions break workspace resolution, or that only Surgeon should modify versions.
|
|
106
|
+
|
|
107
|
+
**Resolution:** This skill, the `prerelease-version-guard` CI gate, and the team decision to centralize version ownership under Surgeon.
|
|
108
|
+
|
|
109
|
+
## Quick Reference
|
|
110
|
+
|
|
111
|
+
| Rule | Summary |
|
|
112
|
+
|------|---------|
|
|
113
|
+
| Format | `MAJOR.MINOR.PATCH` — no prerelease on dev/main |
|
|
114
|
+
| Prerelease | `-build.N` is local-only, never committed |
|
|
115
|
+
| Sync | SDK + CLI + root must have identical versions |
|
|
116
|
+
| Ownership | Surgeon bumps versions; others don't touch them |
|
|
117
|
+
| CI gate | `prerelease-version-guard` blocks prerelease PRs |
|
|
118
|
+
| Escape hatch | Any agent may revert a prerelease leak to clean version |
|
|
119
|
+
| Footgun | `>=0.9.0` does NOT match `0.9.1-build.4` per semver |
|
|
@@ -875,13 +875,15 @@ prompt: |
|
|
|
875
875
|
SPAWN MANIFEST: {spawn_manifest}
|
|
876
876
|
|
|
877
877
|
Tasks (in order):
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
878
|
+
0. PRE-CHECK: Stat decisions.md size and count inbox/ files. Record measurements.
|
|
879
|
+
1. DECISIONS ARCHIVE [HARD GATE]: If decisions.md >= 20480 bytes, archive entries older than 30 days NOW. If >= 51200 bytes, archive entries older than 7 days. Do not skip this step.
|
|
880
|
+
2. DECISION INBOX: Merge .squad/decisions/inbox/ → decisions.md, delete inbox files. Deduplicate.
|
|
881
|
+
3. ORCHESTRATION LOG: Write .squad/orchestration-log/{timestamp}-{agent}.md per agent. Use ISO 8601 UTC timestamp.
|
|
882
|
+
4. SESSION LOG: Write .squad/log/{timestamp}-{topic}.md. Brief. Use ISO 8601 UTC timestamp.
|
|
883
|
+
5. CROSS-AGENT: Append team updates to affected agents' history.md.
|
|
884
|
+
6. HISTORY SUMMARIZATION [HARD GATE]: If any history.md >= 15360 bytes (15KB), summarize now.
|
|
885
|
+
7. GIT COMMIT: Stage only the exact `.squad/` files Scribe wrote in this session. Use `git status --porcelain` filtered to allowed paths (decisions.md, decisions-archive.md, agents/{name}/history.md, agents/{name}/history-archive.md, log/*, orchestration-log/*). Stage each file individually with `git add -- <path>`. Handle renames by extracting destination path (`-replace '^.* -> ',''`). Commit with -F (write msg to temp file). Skip if nothing staged. ⚠️ NEVER use `git add .squad/` or broad globs.
|
|
886
|
+
8. HEALTH REPORT: Log decisions.md before/after size, inbox count processed, history files summarized.
|
|
885
887
|
|
|
886
888
|
Never speak to user. ⚠️ End with plain text summary after all tool calls.
|
|
887
889
|
```
|
|
@@ -110,9 +110,11 @@ jobs:
|
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
function slugify(t) { return t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); }
|
|
114
|
+
|
|
113
115
|
// Build triage context
|
|
114
116
|
const memberList = members.map(m =>
|
|
115
|
-
`- **${m.name}** (${m.role}) → label: \`squad:${m.name
|
|
117
|
+
`- **${m.name}** (${m.role}) → label: \`squad:${slugify(m.name)}\``
|
|
116
118
|
).join('\n');
|
|
117
119
|
|
|
118
120
|
// Determine best assignee based on issue content and routing
|
|
@@ -189,7 +191,7 @@ jobs:
|
|
|
189
191
|
}
|
|
190
192
|
|
|
191
193
|
const isCopilot = assignedMember.name === '@copilot';
|
|
192
|
-
const assignLabel = isCopilot ? 'squad:copilot' : `squad:${assignedMember.name
|
|
194
|
+
const assignLabel = isCopilot ? 'squad:copilot' : `squad:${slugify(assignedMember.name)}`;
|
|
193
195
|
|
|
194
196
|
// Add the member-specific label
|
|
195
197
|
await github.rest.issues.addLabels({
|
|
@@ -103,6 +103,8 @@ jobs:
|
|
|
103
103
|
{ name: 'priority:p2', color: 'FBCA04', description: 'Next sprint' }
|
|
104
104
|
];
|
|
105
105
|
|
|
106
|
+
function slugify(t) { return t.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); }
|
|
107
|
+
|
|
106
108
|
// Ensure the base "squad" triage label exists
|
|
107
109
|
const labels = [
|
|
108
110
|
{ name: 'squad', color: SQUAD_COLOR, description: 'Squad triage inbox — Lead will assign to a member' }
|
|
@@ -110,7 +112,7 @@ jobs:
|
|
|
110
112
|
|
|
111
113
|
for (const member of members) {
|
|
112
114
|
labels.push({
|
|
113
|
-
name: `squad:${member.name
|
|
115
|
+
name: `squad:${slugify(member.name)}`,
|
|
114
116
|
color: MEMBER_COLOR,
|
|
115
117
|
description: `Assigned to ${member.name} (${member.role})`
|
|
116
118
|
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Budget check utility — enforce configurable per-round limits.
|
|
3
|
-
*
|
|
4
|
-
* Ported from ralph-watch.ps1 rate-pool / budget tracking logic.
|
|
5
|
-
*
|
|
6
|
-
* Config (via squad.config.ts → watch.capabilities or CLI flags):
|
|
7
|
-
* maxIssuesPerRound – max issues to execute in a single round (default: 5)
|
|
8
|
-
* maxCostPerRound – cost cap (abstract units) per round (default: Infinity)
|
|
9
|
-
*
|
|
10
|
-
* This is a utility module — not a WatchCapability.
|
|
11
|
-
* Called by the execute capability before spawning agent sessions.
|
|
12
|
-
*/
|
|
13
|
-
export interface BudgetConfig {
|
|
14
|
-
maxIssuesPerRound?: number;
|
|
15
|
-
maxCostPerRound?: number;
|
|
16
|
-
}
|
|
17
|
-
export interface BudgetResult {
|
|
18
|
-
allowed: number;
|
|
19
|
-
reason: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Determine how many issues can be executed this round given budget constraints.
|
|
23
|
-
*
|
|
24
|
-
* @param requested Number of issues the execute phase wants to process.
|
|
25
|
-
* @param config Budget limits from config or CLI.
|
|
26
|
-
* @returns How many are allowed + explanation.
|
|
27
|
-
*/
|
|
28
|
-
export declare function checkBudget(requested: number, config?: BudgetConfig): BudgetResult;
|
|
29
|
-
//# sourceMappingURL=budget-check.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"budget-check.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/budget-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,YAAY;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAmBlF"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Budget check utility — enforce configurable per-round limits.
|
|
3
|
-
*
|
|
4
|
-
* Ported from ralph-watch.ps1 rate-pool / budget tracking logic.
|
|
5
|
-
*
|
|
6
|
-
* Config (via squad.config.ts → watch.capabilities or CLI flags):
|
|
7
|
-
* maxIssuesPerRound – max issues to execute in a single round (default: 5)
|
|
8
|
-
* maxCostPerRound – cost cap (abstract units) per round (default: Infinity)
|
|
9
|
-
*
|
|
10
|
-
* This is a utility module — not a WatchCapability.
|
|
11
|
-
* Called by the execute capability before spawning agent sessions.
|
|
12
|
-
*/
|
|
13
|
-
const DEFAULT_MAX_ISSUES = 5;
|
|
14
|
-
/**
|
|
15
|
-
* Determine how many issues can be executed this round given budget constraints.
|
|
16
|
-
*
|
|
17
|
-
* @param requested Number of issues the execute phase wants to process.
|
|
18
|
-
* @param config Budget limits from config or CLI.
|
|
19
|
-
* @returns How many are allowed + explanation.
|
|
20
|
-
*/
|
|
21
|
-
export function checkBudget(requested, config) {
|
|
22
|
-
const maxIssues = config?.maxIssuesPerRound ?? DEFAULT_MAX_ISSUES;
|
|
23
|
-
const maxCost = config?.maxCostPerRound ?? Infinity;
|
|
24
|
-
// Simple issue-count gate
|
|
25
|
-
if (requested <= maxIssues && maxCost === Infinity) {
|
|
26
|
-
return { allowed: requested, reason: `within budget (${requested}/${maxIssues})` };
|
|
27
|
-
}
|
|
28
|
-
const allowed = Math.min(requested, maxIssues);
|
|
29
|
-
const reasons = [];
|
|
30
|
-
if (requested > maxIssues) {
|
|
31
|
-
reasons.push(`capped to ${maxIssues} issues/round`);
|
|
32
|
-
}
|
|
33
|
-
if (maxCost !== Infinity) {
|
|
34
|
-
reasons.push(`cost cap: ${maxCost}`);
|
|
35
|
-
}
|
|
36
|
-
return { allowed, reason: reasons.join('; ') };
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=budget-check.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"budget-check.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/budget-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAYH,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,MAAqB;IAClE,MAAM,SAAS,GAAG,MAAM,EAAE,iBAAiB,IAAI,kBAAkB,CAAC;IAClE,MAAM,OAAO,GAAG,MAAM,EAAE,eAAe,IAAI,QAAQ,CAAC;IAEpD,0BAA0B;IAC1B,IAAI,SAAS,IAAI,SAAS,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACnD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,kBAAkB,SAAS,IAAI,SAAS,GAAG,EAAE,CAAC;IACrF,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,aAAa,SAAS,eAAe,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACjD,CAAC"}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Enhanced Circuit Breaker capability — model-level fallback with cooldown.
|
|
3
|
-
*
|
|
4
|
-
* Ported from ralph-watch.ps1 `Get-CircuitBreakerState` / `Update-CircuitBreakerOn*`.
|
|
5
|
-
* Tracks model failures, auto-fallback through a configurable chain, and
|
|
6
|
-
* cooldown timer. State persisted to `.squad/ralph-circuit-breaker.json`.
|
|
7
|
-
*
|
|
8
|
-
* This is a **utility module** consumed by the main watch loop, not a
|
|
9
|
-
* phase-based capability (it gates every round, not a specific phase).
|
|
10
|
-
*
|
|
11
|
-
* Config (via squad.config.ts → watch.circuitBreaker):
|
|
12
|
-
* preferredModel – default model to use (default: "claude-sonnet-4")
|
|
13
|
-
* fallbackChain – ordered list of fallback models
|
|
14
|
-
* cooldownMinutes – minutes before half-open probe (default: 10)
|
|
15
|
-
* requiredSuccessesToClose – successes in half-open before closing (default: 2)
|
|
16
|
-
*/
|
|
17
|
-
export type CircuitState = 'closed' | 'open' | 'half-open';
|
|
18
|
-
export interface ModelCircuitBreakerState {
|
|
19
|
-
state: CircuitState;
|
|
20
|
-
preferredModel: string;
|
|
21
|
-
currentModel: string;
|
|
22
|
-
fallbackChain: string[];
|
|
23
|
-
lastRateLimitHit: string | null;
|
|
24
|
-
cooldownMinutes: number;
|
|
25
|
-
consecutiveSuccesses: number;
|
|
26
|
-
requiredSuccessesToClose: number;
|
|
27
|
-
totalFallbacks: number;
|
|
28
|
-
totalRecoveries: number;
|
|
29
|
-
}
|
|
30
|
-
export interface CircuitBreakerConfig {
|
|
31
|
-
preferredModel?: string;
|
|
32
|
-
fallbackChain?: string[];
|
|
33
|
-
cooldownMinutes?: number;
|
|
34
|
-
requiredSuccessesToClose?: number;
|
|
35
|
-
}
|
|
36
|
-
export declare class ModelCircuitBreaker {
|
|
37
|
-
private statePath;
|
|
38
|
-
constructor(squadDir: string, config?: CircuitBreakerConfig);
|
|
39
|
-
load(): ModelCircuitBreakerState;
|
|
40
|
-
save(state: ModelCircuitBreakerState): void;
|
|
41
|
-
/** Get the model to use for the current round. */
|
|
42
|
-
getCurrentModel(): string;
|
|
43
|
-
/** Call after a successful round. */
|
|
44
|
-
onSuccess(): void;
|
|
45
|
-
/** Call when a rate limit or model error is detected. */
|
|
46
|
-
onRateLimit(): void;
|
|
47
|
-
/** Reset to defaults (used by post-failure remediation). */
|
|
48
|
-
reset(): void;
|
|
49
|
-
/** Get full state for diagnostics. */
|
|
50
|
-
getState(): ModelCircuitBreakerState;
|
|
51
|
-
}
|
|
52
|
-
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,YAAY,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wBAAwB,EAAE,MAAM,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAeD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,SAAS,CAAS;gBAEd,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,oBAAoB;IAkB3D,IAAI,IAAI,wBAAwB;IAuBhC,IAAI,CAAC,KAAK,EAAE,wBAAwB,GAAG,IAAI;IAU3C,kDAAkD;IAClD,eAAe,IAAI,MAAM;IAmBzB,qCAAqC;IACrC,SAAS,IAAI,IAAI;IAcjB,yDAAyD;IACzD,WAAW,IAAI,IAAI;IAanB,4DAA4D;IAC5D,KAAK,IAAI,IAAI;IAIb,sCAAsC;IACtC,QAAQ,IAAI,wBAAwB;CAGrC"}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Enhanced Circuit Breaker capability — model-level fallback with cooldown.
|
|
3
|
-
*
|
|
4
|
-
* Ported from ralph-watch.ps1 `Get-CircuitBreakerState` / `Update-CircuitBreakerOn*`.
|
|
5
|
-
* Tracks model failures, auto-fallback through a configurable chain, and
|
|
6
|
-
* cooldown timer. State persisted to `.squad/ralph-circuit-breaker.json`.
|
|
7
|
-
*
|
|
8
|
-
* This is a **utility module** consumed by the main watch loop, not a
|
|
9
|
-
* phase-based capability (it gates every round, not a specific phase).
|
|
10
|
-
*
|
|
11
|
-
* Config (via squad.config.ts → watch.circuitBreaker):
|
|
12
|
-
* preferredModel – default model to use (default: "claude-sonnet-4")
|
|
13
|
-
* fallbackChain – ordered list of fallback models
|
|
14
|
-
* cooldownMinutes – minutes before half-open probe (default: 10)
|
|
15
|
-
* requiredSuccessesToClose – successes in half-open before closing (default: 2)
|
|
16
|
-
*/
|
|
17
|
-
import path from 'node:path';
|
|
18
|
-
import fs from 'node:fs';
|
|
19
|
-
const DEFAULT_STATE = {
|
|
20
|
-
state: 'closed',
|
|
21
|
-
preferredModel: 'claude-sonnet-4',
|
|
22
|
-
currentModel: 'claude-sonnet-4',
|
|
23
|
-
fallbackChain: ['gpt-4.1', 'claude-haiku-4.5'],
|
|
24
|
-
lastRateLimitHit: null,
|
|
25
|
-
cooldownMinutes: 10,
|
|
26
|
-
consecutiveSuccesses: 0,
|
|
27
|
-
requiredSuccessesToClose: 2,
|
|
28
|
-
totalFallbacks: 0,
|
|
29
|
-
totalRecoveries: 0,
|
|
30
|
-
};
|
|
31
|
-
export class ModelCircuitBreaker {
|
|
32
|
-
statePath;
|
|
33
|
-
constructor(squadDir, config) {
|
|
34
|
-
this.statePath = path.join(squadDir, 'ralph-circuit-breaker.json');
|
|
35
|
-
// Ensure defaults incorporate any user config
|
|
36
|
-
if (config) {
|
|
37
|
-
const state = this.load();
|
|
38
|
-
let dirty = false;
|
|
39
|
-
if (config.preferredModel && state.preferredModel !== config.preferredModel) {
|
|
40
|
-
state.preferredModel = config.preferredModel;
|
|
41
|
-
if (state.state === 'closed')
|
|
42
|
-
state.currentModel = config.preferredModel;
|
|
43
|
-
dirty = true;
|
|
44
|
-
}
|
|
45
|
-
if (config.fallbackChain) {
|
|
46
|
-
state.fallbackChain = config.fallbackChain;
|
|
47
|
-
dirty = true;
|
|
48
|
-
}
|
|
49
|
-
if (config.cooldownMinutes) {
|
|
50
|
-
state.cooldownMinutes = config.cooldownMinutes;
|
|
51
|
-
dirty = true;
|
|
52
|
-
}
|
|
53
|
-
if (config.requiredSuccessesToClose) {
|
|
54
|
-
state.requiredSuccessesToClose = config.requiredSuccessesToClose;
|
|
55
|
-
dirty = true;
|
|
56
|
-
}
|
|
57
|
-
if (dirty)
|
|
58
|
-
this.save(state);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
load() {
|
|
62
|
-
try {
|
|
63
|
-
if (!fs.existsSync(this.statePath))
|
|
64
|
-
return { ...DEFAULT_STATE };
|
|
65
|
-
const raw = fs.readFileSync(this.statePath, 'utf-8');
|
|
66
|
-
if (!raw)
|
|
67
|
-
return { ...DEFAULT_STATE };
|
|
68
|
-
const parsed = JSON.parse(raw);
|
|
69
|
-
// Handle legacy nested schema (model_fallback wrapper)
|
|
70
|
-
const legacy = parsed;
|
|
71
|
-
if (!parsed.preferredModel && legacy['model_fallback']) {
|
|
72
|
-
const mf = legacy['model_fallback'];
|
|
73
|
-
return {
|
|
74
|
-
...DEFAULT_STATE,
|
|
75
|
-
preferredModel: mf['preferred'] ?? DEFAULT_STATE.preferredModel,
|
|
76
|
-
currentModel: mf['preferred'] ?? DEFAULT_STATE.currentModel,
|
|
77
|
-
fallbackChain: mf['fallback_chain'] ?? DEFAULT_STATE.fallbackChain,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return { ...DEFAULT_STATE, ...parsed };
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return { ...DEFAULT_STATE };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
save(state) {
|
|
87
|
-
try {
|
|
88
|
-
const dir = path.dirname(this.statePath);
|
|
89
|
-
if (!fs.existsSync(dir)) {
|
|
90
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
-
}
|
|
92
|
-
fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
93
|
-
}
|
|
94
|
-
catch { /* best-effort */ }
|
|
95
|
-
}
|
|
96
|
-
/** Get the model to use for the current round. */
|
|
97
|
-
getCurrentModel() {
|
|
98
|
-
const state = this.load();
|
|
99
|
-
if (state.state === 'closed')
|
|
100
|
-
return state.preferredModel;
|
|
101
|
-
if (state.state === 'open') {
|
|
102
|
-
if (state.lastRateLimitHit) {
|
|
103
|
-
const elapsed = Date.now() - new Date(state.lastRateLimitHit).getTime();
|
|
104
|
-
if (elapsed >= state.cooldownMinutes * 60_000) {
|
|
105
|
-
state.state = 'half-open';
|
|
106
|
-
state.currentModel = state.preferredModel;
|
|
107
|
-
this.save(state);
|
|
108
|
-
return state.preferredModel;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return state.currentModel;
|
|
112
|
-
}
|
|
113
|
-
// half-open — probe preferred
|
|
114
|
-
return state.preferredModel;
|
|
115
|
-
}
|
|
116
|
-
/** Call after a successful round. */
|
|
117
|
-
onSuccess() {
|
|
118
|
-
const state = this.load();
|
|
119
|
-
if (state.state === 'half-open') {
|
|
120
|
-
state.consecutiveSuccesses++;
|
|
121
|
-
if (state.consecutiveSuccesses >= state.requiredSuccessesToClose) {
|
|
122
|
-
state.state = 'closed';
|
|
123
|
-
state.currentModel = state.preferredModel;
|
|
124
|
-
state.consecutiveSuccesses = 0;
|
|
125
|
-
state.totalRecoveries++;
|
|
126
|
-
}
|
|
127
|
-
this.save(state);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/** Call when a rate limit or model error is detected. */
|
|
131
|
-
onRateLimit() {
|
|
132
|
-
const state = this.load();
|
|
133
|
-
state.state = 'open';
|
|
134
|
-
state.lastRateLimitHit = new Date().toISOString();
|
|
135
|
-
state.consecutiveSuccesses = 0;
|
|
136
|
-
state.totalFallbacks++;
|
|
137
|
-
// Pick first fallback
|
|
138
|
-
if (state.fallbackChain.length > 0) {
|
|
139
|
-
state.currentModel = state.fallbackChain[0];
|
|
140
|
-
}
|
|
141
|
-
this.save(state);
|
|
142
|
-
}
|
|
143
|
-
/** Reset to defaults (used by post-failure remediation). */
|
|
144
|
-
reset() {
|
|
145
|
-
this.save({ ...DEFAULT_STATE });
|
|
146
|
-
}
|
|
147
|
-
/** Get full state for diagnostics. */
|
|
148
|
-
getState() {
|
|
149
|
-
return this.load();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health-check capability — pre-round watchdog.
|
|
3
|
-
*
|
|
4
|
-
* Ported from ralph-watch.ps1 `Invoke-RalphHealthCheck`.
|
|
5
|
-
* Runs in the `pre-scan` phase and verifies:
|
|
6
|
-
* 1. gh CLI authenticated
|
|
7
|
-
* 2. Circuit breaker state file is valid
|
|
8
|
-
* 3. Disk space above threshold (configurable, default 500 MB)
|
|
9
|
-
* 4. Git branch matches expected (optional)
|
|
10
|
-
*
|
|
11
|
-
* Config (via squad.config.ts → watch.capabilities["health-check"]):
|
|
12
|
-
* diskThresholdMB – minimum free disk in MB (default: 500)
|
|
13
|
-
* expectedBranch – warn if not on this branch (optional)
|
|
14
|
-
*/
|
|
15
|
-
import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js';
|
|
16
|
-
export interface HealthCheckResult {
|
|
17
|
-
healed: string[];
|
|
18
|
-
warnings: string[];
|
|
19
|
-
}
|
|
20
|
-
export declare class HealthCheckCapability implements WatchCapability {
|
|
21
|
-
readonly name = "health-check";
|
|
22
|
-
readonly description = "Pre-round watchdog: verify auth, disk space, branch, CB state";
|
|
23
|
-
readonly configShape: "object";
|
|
24
|
-
readonly requires: string[];
|
|
25
|
-
readonly phase: "pre-scan";
|
|
26
|
-
preflight(_context: WatchContext): Promise<PreflightResult>;
|
|
27
|
-
execute(context: WatchContext): Promise<CapabilityResult>;
|
|
28
|
-
}
|
|
29
|
-
//# sourceMappingURL=health-check.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/health-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpG,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,QAAQ,CAAC,WAAW,mEAAmE;IACvF,QAAQ,CAAC,WAAW,EAAG,QAAQ,CAAU;IACzC,QAAQ,CAAC,QAAQ,WAAU;IAC3B,QAAQ,CAAC,KAAK,EAAG,UAAU,CAAU;IAE/B,SAAS,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;IAI3D,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA0EhE"}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health-check capability — pre-round watchdog.
|
|
3
|
-
*
|
|
4
|
-
* Ported from ralph-watch.ps1 `Invoke-RalphHealthCheck`.
|
|
5
|
-
* Runs in the `pre-scan` phase and verifies:
|
|
6
|
-
* 1. gh CLI authenticated
|
|
7
|
-
* 2. Circuit breaker state file is valid
|
|
8
|
-
* 3. Disk space above threshold (configurable, default 500 MB)
|
|
9
|
-
* 4. Git branch matches expected (optional)
|
|
10
|
-
*
|
|
11
|
-
* Config (via squad.config.ts → watch.capabilities["health-check"]):
|
|
12
|
-
* diskThresholdMB – minimum free disk in MB (default: 500)
|
|
13
|
-
* expectedBranch – warn if not on this branch (optional)
|
|
14
|
-
*/
|
|
15
|
-
import { execFile } from 'node:child_process';
|
|
16
|
-
import { promisify } from 'node:util';
|
|
17
|
-
import path from 'node:path';
|
|
18
|
-
import fs from 'node:fs';
|
|
19
|
-
const execFileAsync = promisify(execFile);
|
|
20
|
-
export class HealthCheckCapability {
|
|
21
|
-
name = 'health-check';
|
|
22
|
-
description = 'Pre-round watchdog: verify auth, disk space, branch, CB state';
|
|
23
|
-
configShape = 'object';
|
|
24
|
-
requires = ['gh'];
|
|
25
|
-
phase = 'pre-scan';
|
|
26
|
-
async preflight(_context) {
|
|
27
|
-
return { ok: true };
|
|
28
|
-
}
|
|
29
|
-
async execute(context) {
|
|
30
|
-
const healed = [];
|
|
31
|
-
const warnings = [];
|
|
32
|
-
const config = context.config;
|
|
33
|
-
const diskThresholdMB = config['diskThresholdMB'] ?? 500;
|
|
34
|
-
const expectedBranch = config['expectedBranch'];
|
|
35
|
-
// 1. Verify gh auth
|
|
36
|
-
try {
|
|
37
|
-
await execFileAsync('gh', ['auth', 'status'], { timeout: 10_000 });
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Try extracting auth info from git remote (P3 auth fallback)
|
|
41
|
-
const patFromRemote = await extractPatFromRemote(context.teamRoot);
|
|
42
|
-
if (patFromRemote) {
|
|
43
|
-
healed.push('Detected PAT in git remote URL — gh auth may need refresh');
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
warnings.push('gh auth: not authenticated — run "gh auth login"');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// 2. Validate circuit breaker state file
|
|
50
|
-
const cbPath = path.join(context.teamRoot, '.squad', 'ralph-circuit-breaker.json');
|
|
51
|
-
try {
|
|
52
|
-
if (fs.existsSync(cbPath)) {
|
|
53
|
-
const rawContent = fs.readFileSync(cbPath, 'utf-8');
|
|
54
|
-
const parsed = JSON.parse(rawContent);
|
|
55
|
-
// Check for nested schema (legacy)
|
|
56
|
-
if (!parsed.preferredModel && parsed.model_fallback) {
|
|
57
|
-
healed.push('CB schema: would convert nested→flat on next use');
|
|
58
|
-
}
|
|
59
|
-
// Check for empty model
|
|
60
|
-
if (parsed.currentModel === '' || parsed.currentModel === null) {
|
|
61
|
-
warnings.push('CB state has empty currentModel — circuit-breaker will auto-reset');
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// No CB file is fine — will be created on demand
|
|
67
|
-
}
|
|
68
|
-
// 3. Disk space check
|
|
69
|
-
try {
|
|
70
|
-
const freeBytes = await getFreeDiskSpace(context.teamRoot);
|
|
71
|
-
const freeMB = Math.floor(freeBytes / (1024 * 1024));
|
|
72
|
-
if (freeMB < diskThresholdMB) {
|
|
73
|
-
warnings.push(`Low disk space: ${freeMB} MB free (threshold: ${diskThresholdMB} MB)`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// Disk check failed — non-blocking
|
|
78
|
-
}
|
|
79
|
-
// 4. Branch drift check
|
|
80
|
-
if (expectedBranch) {
|
|
81
|
-
try {
|
|
82
|
-
const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
83
|
-
cwd: context.teamRoot,
|
|
84
|
-
timeout: 5_000,
|
|
85
|
-
});
|
|
86
|
-
const current = stdout.trim();
|
|
87
|
-
if (current && current !== expectedBranch) {
|
|
88
|
-
warnings.push(`Branch drift: on "${current}" (expected "${expectedBranch}")`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch { /* not a git repo or other error */ }
|
|
92
|
-
}
|
|
93
|
-
const summary = healed.length > 0 || warnings.length > 0
|
|
94
|
-
? `healed: ${healed.length}, warnings: ${warnings.length}`
|
|
95
|
-
: 'all checks passed';
|
|
96
|
-
return {
|
|
97
|
-
success: warnings.length === 0,
|
|
98
|
-
summary,
|
|
99
|
-
data: { healed, warnings },
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
/** Try extracting a PAT from git remote URL (https://<pat>@github.com/...) */
|
|
104
|
-
async function extractPatFromRemote(cwd) {
|
|
105
|
-
try {
|
|
106
|
-
const { stdout } = await execFileAsync('git', ['config', '--get', 'remote.origin.url'], {
|
|
107
|
-
cwd,
|
|
108
|
-
timeout: 5_000,
|
|
109
|
-
});
|
|
110
|
-
const url = stdout.trim();
|
|
111
|
-
// Pattern: https://<token>@github.com/...
|
|
112
|
-
const match = url.match(/https:\/\/([^@]+)@github\.com/);
|
|
113
|
-
if (match && match[1] && match[1] !== 'oauth2') {
|
|
114
|
-
return match[1];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch { /* not available */ }
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
/** Get free disk space in bytes for the drive containing the given path. */
|
|
121
|
-
async function getFreeDiskSpace(dirPath) {
|
|
122
|
-
const platform = process.platform;
|
|
123
|
-
if (platform === 'win32') {
|
|
124
|
-
const drive = path.parse(path.resolve(dirPath)).root;
|
|
125
|
-
const { stdout } = await execFileAsync('wmic', [
|
|
126
|
-
'logicaldisk', 'where', `DeviceID='${drive.replace('\\', '')}'`,
|
|
127
|
-
'get', 'FreeSpace', '/value',
|
|
128
|
-
], { timeout: 5_000 });
|
|
129
|
-
const match = stdout.match(/FreeSpace=(\d+)/);
|
|
130
|
-
return match ? parseInt(match[1], 10) : Infinity;
|
|
131
|
-
}
|
|
132
|
-
// Unix: use df
|
|
133
|
-
const { stdout } = await execFileAsync('df', ['--output=avail', '-B1', dirPath], {
|
|
134
|
-
timeout: 5_000,
|
|
135
|
-
});
|
|
136
|
-
const lines = stdout.trim().split('\n');
|
|
137
|
-
return parseInt(lines[lines.length - 1].trim(), 10) || Infinity;
|
|
138
|
-
}
|
|
139
|
-
//# sourceMappingURL=health-check.js.map
|