@bradygaster/squad-cli 0.9.2-insider.5 → 0.9.2-insider.6
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/dist/cli/commands/watch/capabilities/budget-check.d.ts +29 -0
- package/dist/cli/commands/watch/capabilities/budget-check.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/budget-check.js +38 -0
- package/dist/cli/commands/watch/capabilities/budget-check.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts +52 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.js +152 -0
- package/dist/cli/commands/watch/capabilities/circuit-breaker.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/execute.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/execute.js +38 -1
- package/dist/cli/commands/watch/capabilities/execute.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/health-check.d.ts +29 -0
- package/dist/cli/commands/watch/capabilities/health-check.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/health-check.js +139 -0
- package/dist/cli/commands/watch/capabilities/health-check.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.d.ts +48 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.js +115 -0
- package/dist/cli/commands/watch/capabilities/heartbeat.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/index.d.ts +2 -0
- package/dist/cli/commands/watch/capabilities/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/capabilities/index.js +12 -0
- package/dist/cli/commands/watch/capabilities/index.js.map +1 -1
- package/dist/cli/commands/watch/capabilities/lockfile.d.ts +30 -0
- package/dist/cli/commands/watch/capabilities/lockfile.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/lockfile.js +100 -0
- package/dist/cli/commands/watch/capabilities/lockfile.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts +30 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.js +103 -0
- package/dist/cli/commands/watch/capabilities/machine-capabilities.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/post-failure.d.ts +19 -0
- package/dist/cli/commands/watch/capabilities/post-failure.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/post-failure.js +58 -0
- package/dist/cli/commands/watch/capabilities/post-failure.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/priority.d.ts +59 -0
- package/dist/cli/commands/watch/capabilities/priority.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/priority.js +101 -0
- package/dist/cli/commands/watch/capabilities/priority.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.d.ts +67 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.js +187 -0
- package/dist/cli/commands/watch/capabilities/rate-pool.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts +23 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.js +87 -0
- package/dist/cli/commands/watch/capabilities/stale-reclaim.js.map +1 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts +29 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.d.ts.map +1 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.js +114 -0
- package/dist/cli/commands/watch/capabilities/webhook-alerts.js.map +1 -0
- package/dist/cli/commands/watch/config.d.ts +15 -0
- package/dist/cli/commands/watch/config.d.ts.map +1 -1
- package/dist/cli/commands/watch/config.js +48 -1
- package/dist/cli/commands/watch/config.js.map +1 -1
- package/dist/cli/commands/watch/index.d.ts +14 -0
- package/dist/cli/commands/watch/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/index.js +55 -2
- package/dist/cli/commands/watch/index.js.map +1 -1
- package/dist/cli/core/nap.d.ts.map +1 -1
- package/dist/cli/core/nap.js +9 -4
- package/dist/cli/core/nap.js.map +1 -1
- package/dist/cli-entry.js +53 -0
- package/dist/cli-entry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue priority scoring — weigh issues for execution order.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 issue scoring logic.
|
|
5
|
+
* Factors:
|
|
6
|
+
* - Priority labels: P0 (100), P1 (60), P2 (30), P3 (10)
|
|
7
|
+
* - Age bonus: +1 per day open (max +30)
|
|
8
|
+
* - Staleness: +20 if no activity in 7+ days
|
|
9
|
+
* - Bug label: +15
|
|
10
|
+
* - Size labels: size:S +10, size:M 0, size:L -5
|
|
11
|
+
*
|
|
12
|
+
* This is a utility module — not a WatchCapability.
|
|
13
|
+
* Used by the execute capability to sort issues before picking work.
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_WEIGHTS = {
|
|
16
|
+
p0: 100,
|
|
17
|
+
p1: 60,
|
|
18
|
+
p2: 30,
|
|
19
|
+
p3: 10,
|
|
20
|
+
agePerDay: 1,
|
|
21
|
+
ageMax: 30,
|
|
22
|
+
staleThresholdDays: 7,
|
|
23
|
+
staleBonus: 20,
|
|
24
|
+
bugBonus: 15,
|
|
25
|
+
sizeSBonus: 10,
|
|
26
|
+
sizeLPenalty: -5,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Score a single issue for execution priority.
|
|
30
|
+
* Higher score = should be picked first.
|
|
31
|
+
*/
|
|
32
|
+
export function scoreIssue(issue, config) {
|
|
33
|
+
const w = { ...DEFAULT_WEIGHTS, ...(config?.weights ?? {}) };
|
|
34
|
+
const breakdown = {};
|
|
35
|
+
let score = 0;
|
|
36
|
+
const labels = new Set(issue.labels.map(l => l.name.toLowerCase()));
|
|
37
|
+
// Priority label scoring
|
|
38
|
+
if (labels.has('p0') || labels.has('priority:p0') || labels.has('priority:critical')) {
|
|
39
|
+
breakdown['priority'] = w.p0;
|
|
40
|
+
}
|
|
41
|
+
else if (labels.has('p1') || labels.has('priority:p1') || labels.has('priority:high')) {
|
|
42
|
+
breakdown['priority'] = w.p1;
|
|
43
|
+
}
|
|
44
|
+
else if (labels.has('p2') || labels.has('priority:p2') || labels.has('priority:medium')) {
|
|
45
|
+
breakdown['priority'] = w.p2;
|
|
46
|
+
}
|
|
47
|
+
else if (labels.has('p3') || labels.has('priority:p3') || labels.has('priority:low')) {
|
|
48
|
+
breakdown['priority'] = w.p3;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
breakdown['priority'] = w.p2; // Default to P2 if unlabeled
|
|
52
|
+
}
|
|
53
|
+
score += breakdown['priority'];
|
|
54
|
+
// Age bonus
|
|
55
|
+
if (issue.createdAt) {
|
|
56
|
+
const created = new Date(issue.createdAt);
|
|
57
|
+
const ageDays = Math.floor((Date.now() - created.getTime()) / (1000 * 60 * 60 * 24));
|
|
58
|
+
const ageScore = Math.min(ageDays * w.agePerDay, w.ageMax);
|
|
59
|
+
breakdown['age'] = ageScore;
|
|
60
|
+
score += ageScore;
|
|
61
|
+
}
|
|
62
|
+
// Staleness bonus
|
|
63
|
+
if (issue.updatedAt) {
|
|
64
|
+
const updated = new Date(issue.updatedAt);
|
|
65
|
+
const staleDays = Math.floor((Date.now() - updated.getTime()) / (1000 * 60 * 60 * 24));
|
|
66
|
+
if (staleDays >= w.staleThresholdDays) {
|
|
67
|
+
breakdown['stale'] = w.staleBonus;
|
|
68
|
+
score += w.staleBonus;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Bug label bonus
|
|
72
|
+
if (labels.has('bug') || labels.has('type:bug')) {
|
|
73
|
+
breakdown['bug'] = w.bugBonus;
|
|
74
|
+
score += w.bugBonus;
|
|
75
|
+
}
|
|
76
|
+
// Size label adjustment
|
|
77
|
+
if (labels.has('size:s') || labels.has('size:small')) {
|
|
78
|
+
breakdown['size'] = w.sizeSBonus;
|
|
79
|
+
score += w.sizeSBonus;
|
|
80
|
+
}
|
|
81
|
+
else if (labels.has('size:l') || labels.has('size:large') || labels.has('size:xl')) {
|
|
82
|
+
breakdown['size'] = w.sizeLPenalty;
|
|
83
|
+
score += w.sizeLPenalty;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
number: issue.number,
|
|
87
|
+
title: issue.title,
|
|
88
|
+
labels: issue.labels,
|
|
89
|
+
score,
|
|
90
|
+
breakdown,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Score and sort a batch of issues, highest score first.
|
|
95
|
+
*/
|
|
96
|
+
export function rankIssues(issues, config) {
|
|
97
|
+
return issues
|
|
98
|
+
.map(i => scoreIssue(i, config))
|
|
99
|
+
.sort((a, b) => b.score - a.score);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=priority.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priority.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/priority.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative Rate Pool — shared API call budget across Ralph instances.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 `New-RatePool` / `Read-RatePool` /
|
|
5
|
+
* `Write-RatePool` / `Update-RatePool` budget coordination logic.
|
|
6
|
+
*
|
|
7
|
+
* Multiple Ralph instances on different machines (or in different
|
|
8
|
+
* terminals) share a single `.squad/ralph-rate-pool.json` file.
|
|
9
|
+
* Advisory file-based locking (read-modify-write with PID stamps)
|
|
10
|
+
* keeps the pool consistent without an external lock manager.
|
|
11
|
+
*
|
|
12
|
+
* Config (via squad.config.ts → watch.ratePool):
|
|
13
|
+
* maxCallsPerInterval – max API calls in the window (default: 50)
|
|
14
|
+
* intervalSeconds – window length in seconds (default: 600)
|
|
15
|
+
* poolFile – override path (default: .squad/ralph-rate-pool.json)
|
|
16
|
+
*/
|
|
17
|
+
export interface RatePoolConfig {
|
|
18
|
+
maxCallsPerInterval?: number;
|
|
19
|
+
intervalSeconds?: number;
|
|
20
|
+
poolFile?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RatePoolMachineEntry {
|
|
23
|
+
lastActive: string;
|
|
24
|
+
pid: number;
|
|
25
|
+
slotsHeld: number;
|
|
26
|
+
}
|
|
27
|
+
export interface RatePoolState {
|
|
28
|
+
windowStart: string;
|
|
29
|
+
slotsUsed: number;
|
|
30
|
+
maxSlots: number;
|
|
31
|
+
intervalSeconds: number;
|
|
32
|
+
machines: Record<string, RatePoolMachineEntry>;
|
|
33
|
+
}
|
|
34
|
+
export interface PoolStatus {
|
|
35
|
+
available: boolean;
|
|
36
|
+
slotsUsed: number;
|
|
37
|
+
maxSlots: number;
|
|
38
|
+
remaining: number;
|
|
39
|
+
windowSecondsLeft: number;
|
|
40
|
+
activeMachines: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class RatePool {
|
|
43
|
+
private readonly poolPath;
|
|
44
|
+
private readonly maxSlots;
|
|
45
|
+
private readonly intervalSeconds;
|
|
46
|
+
constructor(squadDir: string, config?: RatePoolConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Try to acquire a slot from the shared pool.
|
|
49
|
+
* Returns `true` if a slot was reserved, `false` if budget is exhausted.
|
|
50
|
+
*/
|
|
51
|
+
acquireSlot(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Release a slot back to the pool.
|
|
54
|
+
* Safe to call even if no slot is held (clamps to zero).
|
|
55
|
+
*/
|
|
56
|
+
releaseSlot(): void;
|
|
57
|
+
/** Return a snapshot of the current pool status. */
|
|
58
|
+
getPoolStatus(): PoolStatus;
|
|
59
|
+
private newPool;
|
|
60
|
+
/** Read with retry + window-expiry reset (mirrors Read-RatePool). */
|
|
61
|
+
private read;
|
|
62
|
+
/** Atomic write: temp file → rename (mirrors Write-RatePool). */
|
|
63
|
+
private write;
|
|
64
|
+
private touchMachine;
|
|
65
|
+
private pruneStale;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=rate-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-pool.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/rate-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,MAAM,WAAW,cAAc;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAsBD,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAE7B,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc;IASrD;;;OAGG;IACH,WAAW,IAAI,OAAO;IAatB;;;OAGG;IACH,WAAW,IAAI,IAAI;IAOnB,oDAAoD;IACpD,aAAa,IAAI,UAAU;IAoB3B,OAAO,CAAC,OAAO;IAUf,qEAAqE;IACrE,OAAO,CAAC,IAAI;IA8CZ,iEAAiE;IACjE,OAAO,CAAC,KAAK;IAgCb,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,UAAU;CAQnB"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cooperative Rate Pool — shared API call budget across Ralph instances.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 `New-RatePool` / `Read-RatePool` /
|
|
5
|
+
* `Write-RatePool` / `Update-RatePool` budget coordination logic.
|
|
6
|
+
*
|
|
7
|
+
* Multiple Ralph instances on different machines (or in different
|
|
8
|
+
* terminals) share a single `.squad/ralph-rate-pool.json` file.
|
|
9
|
+
* Advisory file-based locking (read-modify-write with PID stamps)
|
|
10
|
+
* keeps the pool consistent without an external lock manager.
|
|
11
|
+
*
|
|
12
|
+
* Config (via squad.config.ts → watch.ratePool):
|
|
13
|
+
* maxCallsPerInterval – max API calls in the window (default: 50)
|
|
14
|
+
* intervalSeconds – window length in seconds (default: 600)
|
|
15
|
+
* poolFile – override path (default: .squad/ralph-rate-pool.json)
|
|
16
|
+
*/
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
// ── Defaults ─────────────────────────────────────────────────────────
|
|
21
|
+
const DEFAULT_MAX_CALLS = 50;
|
|
22
|
+
const DEFAULT_INTERVAL_SECONDS = 600; // 10 minutes
|
|
23
|
+
const MAX_RETRIES = 3;
|
|
24
|
+
const RETRY_BASE_MS = 200;
|
|
25
|
+
const STALE_MACHINE_MS = 30 * 60 * 1000; // 30 minutes
|
|
26
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
27
|
+
function machineId() {
|
|
28
|
+
return `${os.hostname()}-${process.pid}`;
|
|
29
|
+
}
|
|
30
|
+
function nowISO() {
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
33
|
+
// ── Rate Pool Class ──────────────────────────────────────────────────
|
|
34
|
+
export class RatePool {
|
|
35
|
+
poolPath;
|
|
36
|
+
maxSlots;
|
|
37
|
+
intervalSeconds;
|
|
38
|
+
constructor(squadDir, config) {
|
|
39
|
+
this.poolPath =
|
|
40
|
+
config?.poolFile ?? path.join(squadDir, 'ralph-rate-pool.json');
|
|
41
|
+
this.maxSlots = config?.maxCallsPerInterval ?? DEFAULT_MAX_CALLS;
|
|
42
|
+
this.intervalSeconds = config?.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS;
|
|
43
|
+
}
|
|
44
|
+
// ── Public API ───────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Try to acquire a slot from the shared pool.
|
|
47
|
+
* Returns `true` if a slot was reserved, `false` if budget is exhausted.
|
|
48
|
+
*/
|
|
49
|
+
acquireSlot() {
|
|
50
|
+
const pool = this.read();
|
|
51
|
+
if (pool.slotsUsed >= pool.maxSlots) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
pool.slotsUsed++;
|
|
55
|
+
this.touchMachine(pool, 1);
|
|
56
|
+
this.write(pool);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Release a slot back to the pool.
|
|
61
|
+
* Safe to call even if no slot is held (clamps to zero).
|
|
62
|
+
*/
|
|
63
|
+
releaseSlot() {
|
|
64
|
+
const pool = this.read();
|
|
65
|
+
pool.slotsUsed = Math.max(0, pool.slotsUsed - 1);
|
|
66
|
+
this.touchMachine(pool, -1);
|
|
67
|
+
this.write(pool);
|
|
68
|
+
}
|
|
69
|
+
/** Return a snapshot of the current pool status. */
|
|
70
|
+
getPoolStatus() {
|
|
71
|
+
const pool = this.read();
|
|
72
|
+
const elapsed = (Date.now() - new Date(pool.windowStart).getTime()) / 1000;
|
|
73
|
+
const windowLeft = Math.max(0, pool.intervalSeconds - elapsed);
|
|
74
|
+
const activeMachines = Object.values(pool.machines).filter((m) => Date.now() - new Date(m.lastActive).getTime() < STALE_MACHINE_MS).length;
|
|
75
|
+
return {
|
|
76
|
+
available: pool.slotsUsed < pool.maxSlots,
|
|
77
|
+
slotsUsed: pool.slotsUsed,
|
|
78
|
+
maxSlots: pool.maxSlots,
|
|
79
|
+
remaining: Math.max(0, pool.maxSlots - pool.slotsUsed),
|
|
80
|
+
windowSecondsLeft: Math.round(windowLeft),
|
|
81
|
+
activeMachines,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// ── Persistence ──────────────────────────────────────────────────
|
|
85
|
+
newPool() {
|
|
86
|
+
return {
|
|
87
|
+
windowStart: nowISO(),
|
|
88
|
+
slotsUsed: 0,
|
|
89
|
+
maxSlots: this.maxSlots,
|
|
90
|
+
intervalSeconds: this.intervalSeconds,
|
|
91
|
+
machines: {},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Read with retry + window-expiry reset (mirrors Read-RatePool). */
|
|
95
|
+
read() {
|
|
96
|
+
if (!fs.existsSync(this.poolPath)) {
|
|
97
|
+
const pool = this.newPool();
|
|
98
|
+
this.write(pool);
|
|
99
|
+
return pool;
|
|
100
|
+
}
|
|
101
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
102
|
+
try {
|
|
103
|
+
const raw = fs.readFileSync(this.poolPath, 'utf-8');
|
|
104
|
+
const pool = JSON.parse(raw);
|
|
105
|
+
// Apply authoritative config (pool file may have been created by
|
|
106
|
+
// another instance with different settings — this instance's config wins)
|
|
107
|
+
pool.maxSlots = this.maxSlots;
|
|
108
|
+
pool.intervalSeconds = this.intervalSeconds;
|
|
109
|
+
// Window expiry check
|
|
110
|
+
const elapsed = (Date.now() - new Date(pool.windowStart).getTime()) / 1000;
|
|
111
|
+
if (elapsed >= pool.intervalSeconds) {
|
|
112
|
+
pool.windowStart = nowISO();
|
|
113
|
+
pool.slotsUsed = 0;
|
|
114
|
+
this.pruneStale(pool);
|
|
115
|
+
this.write(pool);
|
|
116
|
+
}
|
|
117
|
+
return pool;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
if (i < MAX_RETRIES - 1) {
|
|
121
|
+
// Busy-wait with escalating backoff (matches PS1 pattern)
|
|
122
|
+
const waitMs = RETRY_BASE_MS * (i + 1);
|
|
123
|
+
const end = Date.now() + waitMs;
|
|
124
|
+
while (Date.now() < end) {
|
|
125
|
+
/* spin */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// All retries exhausted — create fresh pool
|
|
131
|
+
const pool = this.newPool();
|
|
132
|
+
this.write(pool);
|
|
133
|
+
return pool;
|
|
134
|
+
}
|
|
135
|
+
/** Atomic write: temp file → rename (mirrors Write-RatePool). */
|
|
136
|
+
write(pool) {
|
|
137
|
+
const dir = path.dirname(this.poolPath);
|
|
138
|
+
if (!fs.existsSync(dir)) {
|
|
139
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
const tmpFile = `${this.poolPath}.tmp.${process.pid}`;
|
|
142
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
143
|
+
try {
|
|
144
|
+
fs.writeFileSync(tmpFile, JSON.stringify(pool, null, 2), 'utf-8');
|
|
145
|
+
fs.renameSync(tmpFile, this.poolPath);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
if (i >= MAX_RETRIES - 1) {
|
|
150
|
+
try {
|
|
151
|
+
fs.unlinkSync(tmpFile);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* ignore */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const waitMs = RETRY_BASE_MS * (i + 1);
|
|
159
|
+
const end = Date.now() + waitMs;
|
|
160
|
+
while (Date.now() < end) {
|
|
161
|
+
/* spin */
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ── Machine tracking ─────────────────────────────────────────────
|
|
168
|
+
touchMachine(pool, slotDelta) {
|
|
169
|
+
const id = machineId();
|
|
170
|
+
const existing = pool.machines[id];
|
|
171
|
+
const held = Math.max(0, (existing?.slotsHeld ?? 0) + slotDelta);
|
|
172
|
+
pool.machines[id] = {
|
|
173
|
+
lastActive: nowISO(),
|
|
174
|
+
pid: process.pid,
|
|
175
|
+
slotsHeld: held,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
pruneStale(pool) {
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
for (const [id, entry] of Object.entries(pool.machines)) {
|
|
181
|
+
if (now - new Date(entry.lastActive).getTime() > STALE_MACHINE_MS) {
|
|
182
|
+
delete pool.machines[id];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=rate-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-pool.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/rate-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stale work reclaim capability — find assigned issues with no activity
|
|
3
|
+
* and unassign them so they can be re-queued.
|
|
4
|
+
*
|
|
5
|
+
* Ported from ralph-watch.ps1 `Get-StaleIssues`.
|
|
6
|
+
*
|
|
7
|
+
* Runs in the `pre-scan` phase.
|
|
8
|
+
*
|
|
9
|
+
* Config (via squad.config.ts → watch.capabilities["stale-reclaim"]):
|
|
10
|
+
* staleHours – hours since last update to consider stale (default: 24)
|
|
11
|
+
* dryRun – log but don't actually unassign (default: false)
|
|
12
|
+
*/
|
|
13
|
+
import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js';
|
|
14
|
+
export declare class StaleReclaimCapability implements WatchCapability {
|
|
15
|
+
readonly name = "stale-reclaim";
|
|
16
|
+
readonly description = "Reclaim issues assigned >24h with no activity";
|
|
17
|
+
readonly configShape: "object";
|
|
18
|
+
readonly requires: string[];
|
|
19
|
+
readonly phase: "pre-scan";
|
|
20
|
+
preflight(_context: WatchContext): Promise<PreflightResult>;
|
|
21
|
+
execute(context: WatchContext): Promise<CapabilityResult>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=stale-reclaim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale-reclaim.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/stale-reclaim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpG,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,WAAW,mDAAmD;IACvE,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;CAoEhE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stale work reclaim capability — find assigned issues with no activity
|
|
3
|
+
* and unassign them so they can be re-queued.
|
|
4
|
+
*
|
|
5
|
+
* Ported from ralph-watch.ps1 `Get-StaleIssues`.
|
|
6
|
+
*
|
|
7
|
+
* Runs in the `pre-scan` phase.
|
|
8
|
+
*
|
|
9
|
+
* Config (via squad.config.ts → watch.capabilities["stale-reclaim"]):
|
|
10
|
+
* staleHours – hours since last update to consider stale (default: 24)
|
|
11
|
+
* dryRun – log but don't actually unassign (default: false)
|
|
12
|
+
*/
|
|
13
|
+
import { execFile } from 'node:child_process';
|
|
14
|
+
import { promisify } from 'node:util';
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
export class StaleReclaimCapability {
|
|
17
|
+
name = 'stale-reclaim';
|
|
18
|
+
description = 'Reclaim issues assigned >24h with no activity';
|
|
19
|
+
configShape = 'object';
|
|
20
|
+
requires = ['gh'];
|
|
21
|
+
phase = 'pre-scan';
|
|
22
|
+
async preflight(_context) {
|
|
23
|
+
return { ok: true };
|
|
24
|
+
}
|
|
25
|
+
async execute(context) {
|
|
26
|
+
const config = context.config;
|
|
27
|
+
const staleHours = config['staleHours'] ?? 24;
|
|
28
|
+
const dryRun = config['dryRun'] ?? false;
|
|
29
|
+
const cutoff = new Date(Date.now() - staleHours * 60 * 60 * 1000);
|
|
30
|
+
let reclaimed = 0;
|
|
31
|
+
try {
|
|
32
|
+
// List open squad-labeled issues that have assignees
|
|
33
|
+
const { stdout } = await execFileAsync('gh', [
|
|
34
|
+
'issue', 'list',
|
|
35
|
+
'--label', 'squad',
|
|
36
|
+
'--state', 'open',
|
|
37
|
+
'--json', 'number,title,assignees,updatedAt',
|
|
38
|
+
'--limit', '50',
|
|
39
|
+
], { cwd: context.teamRoot, timeout: 30_000 });
|
|
40
|
+
const issues = JSON.parse(stdout);
|
|
41
|
+
for (const issue of issues) {
|
|
42
|
+
if (!issue.assignees || issue.assignees.length === 0)
|
|
43
|
+
continue;
|
|
44
|
+
const lastUpdate = new Date(issue.updatedAt);
|
|
45
|
+
if (lastUpdate >= cutoff)
|
|
46
|
+
continue;
|
|
47
|
+
const staleHrs = Math.round((Date.now() - lastUpdate.getTime()) / (1000 * 60 * 60));
|
|
48
|
+
if (dryRun) {
|
|
49
|
+
console.log(` [stale-reclaim] Would unassign #${issue.number} "${issue.title}" (stale ${staleHrs}h)`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
try {
|
|
53
|
+
// Unassign all current assignees
|
|
54
|
+
for (const assignee of issue.assignees) {
|
|
55
|
+
await execFileAsync('gh', [
|
|
56
|
+
'issue', 'edit', String(issue.number),
|
|
57
|
+
'--remove-assignee', assignee.login,
|
|
58
|
+
], { cwd: context.teamRoot, timeout: 10_000 });
|
|
59
|
+
}
|
|
60
|
+
// Add a comment so there's an audit trail
|
|
61
|
+
await execFileAsync('gh', [
|
|
62
|
+
'issue', 'comment', String(issue.number),
|
|
63
|
+
'--body', `🔄 Auto-reclaimed by watch (stale ${staleHrs}h, threshold ${staleHours}h). Re-queued for assignment.`,
|
|
64
|
+
], { cwd: context.teamRoot, timeout: 10_000 });
|
|
65
|
+
reclaimed++;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// best-effort per issue
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const mode = dryRun ? ' (dry-run)' : '';
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
summary: `${reclaimed} issue(s) reclaimed${mode}`,
|
|
76
|
+
data: { reclaimed },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
summary: `stale-reclaim failed: ${e.message}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=stale-reclaim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stale-reclaim.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/stale-reclaim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook alerts capability — POST to a webhook URL on consecutive failures.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 Teams alert logic in `Invoke-PostFailureRemediation`.
|
|
5
|
+
* Org-agnostic: works with any webhook that accepts JSON POST (Slack, Discord,
|
|
6
|
+
* Teams Incoming Webhook, generic HTTP endpoint).
|
|
7
|
+
*
|
|
8
|
+
* Runs in the `housekeeping` phase.
|
|
9
|
+
*
|
|
10
|
+
* Config (via squad.config.ts → watch.capabilities["webhook-alerts"]):
|
|
11
|
+
* webhookUrl – URL to POST to (also settable via --webhook-url flag)
|
|
12
|
+
* alertThreshold – consecutive failures before alerting (default: 3, also --alert-threshold)
|
|
13
|
+
* includeHostname – include machine hostname in payload (default: true)
|
|
14
|
+
*
|
|
15
|
+
* CLI flags:
|
|
16
|
+
* --webhook-url <url> Override webhook URL
|
|
17
|
+
* --alert-threshold <n> Override failure threshold
|
|
18
|
+
*/
|
|
19
|
+
import type { WatchCapability, WatchContext, PreflightResult, CapabilityResult } from '../types.js';
|
|
20
|
+
export declare class WebhookAlertCapability implements WatchCapability {
|
|
21
|
+
readonly name = "webhook-alerts";
|
|
22
|
+
readonly description = "POST to webhook on consecutive failures above threshold";
|
|
23
|
+
readonly configShape: "object";
|
|
24
|
+
readonly requires: never[];
|
|
25
|
+
readonly phase: "housekeeping";
|
|
26
|
+
preflight(context: WatchContext): Promise<PreflightResult>;
|
|
27
|
+
execute(context: WatchContext): Promise<CapabilityResult>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=webhook-alerts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-alerts.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/webhook-alerts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpG,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,WAAW,6DAA6D;IACjF,QAAQ,CAAC,WAAW,EAAG,QAAQ,CAAU;IACzC,QAAQ,CAAC,QAAQ,UAAM;IACvB,QAAQ,CAAC,KAAK,EAAG,cAAc,CAAU;IAEnC,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC;IAS1D,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAkFhE"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook alerts capability — POST to a webhook URL on consecutive failures.
|
|
3
|
+
*
|
|
4
|
+
* Ported from ralph-watch.ps1 Teams alert logic in `Invoke-PostFailureRemediation`.
|
|
5
|
+
* Org-agnostic: works with any webhook that accepts JSON POST (Slack, Discord,
|
|
6
|
+
* Teams Incoming Webhook, generic HTTP endpoint).
|
|
7
|
+
*
|
|
8
|
+
* Runs in the `housekeeping` phase.
|
|
9
|
+
*
|
|
10
|
+
* Config (via squad.config.ts → watch.capabilities["webhook-alerts"]):
|
|
11
|
+
* webhookUrl – URL to POST to (also settable via --webhook-url flag)
|
|
12
|
+
* alertThreshold – consecutive failures before alerting (default: 3, also --alert-threshold)
|
|
13
|
+
* includeHostname – include machine hostname in payload (default: true)
|
|
14
|
+
*
|
|
15
|
+
* CLI flags:
|
|
16
|
+
* --webhook-url <url> Override webhook URL
|
|
17
|
+
* --alert-threshold <n> Override failure threshold
|
|
18
|
+
*/
|
|
19
|
+
import * as os from 'node:os';
|
|
20
|
+
import { getConsecutiveFailures } from './heartbeat.js';
|
|
21
|
+
export class WebhookAlertCapability {
|
|
22
|
+
name = 'webhook-alerts';
|
|
23
|
+
description = 'POST to webhook on consecutive failures above threshold';
|
|
24
|
+
configShape = 'object';
|
|
25
|
+
requires = [];
|
|
26
|
+
phase = 'housekeeping';
|
|
27
|
+
async preflight(context) {
|
|
28
|
+
const config = context.config;
|
|
29
|
+
const url = config['webhookUrl'];
|
|
30
|
+
if (!url) {
|
|
31
|
+
return { ok: false, reason: 'No webhookUrl configured (use --webhook-url or config)' };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
async execute(context) {
|
|
36
|
+
const config = context.config;
|
|
37
|
+
const webhookUrl = config['webhookUrl'];
|
|
38
|
+
const threshold = config['alertThreshold'] ?? 3;
|
|
39
|
+
const includeHostname = config['includeHostname'] ?? true;
|
|
40
|
+
const failures = getConsecutiveFailures();
|
|
41
|
+
if (failures < threshold) {
|
|
42
|
+
return {
|
|
43
|
+
success: true,
|
|
44
|
+
summary: `${failures} failure(s) — below threshold (${threshold})`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Build generic JSON payload (works with Slack, Discord, Teams, etc.)
|
|
48
|
+
const hostname = includeHostname ? os.hostname() : 'unknown';
|
|
49
|
+
const payload = {
|
|
50
|
+
text: `🚨 Squad Watch Alert: ${failures} consecutive failures on ${hostname} (round ${context.round})`,
|
|
51
|
+
// Slack-compatible fields
|
|
52
|
+
blocks: [
|
|
53
|
+
{
|
|
54
|
+
type: 'section',
|
|
55
|
+
text: {
|
|
56
|
+
type: 'mrkdwn',
|
|
57
|
+
text: `*🚨 Squad Watch Alert*\n${failures} consecutive failures`,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'section',
|
|
62
|
+
fields: [
|
|
63
|
+
{ type: 'mrkdwn', text: `*Machine:* ${hostname}` },
|
|
64
|
+
{ type: 'mrkdwn', text: `*Round:* ${context.round}` },
|
|
65
|
+
{ type: 'mrkdwn', text: `*Failures:* ${failures}` },
|
|
66
|
+
{ type: 'mrkdwn', text: `*Timestamp:* ${new Date().toISOString()}` },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
// Teams-compatible fields (MessageCard format)
|
|
71
|
+
'@type': 'MessageCard',
|
|
72
|
+
'@context': 'https://schema.org/extensions',
|
|
73
|
+
summary: `Squad Watch: ${failures} consecutive failures on ${hostname}`,
|
|
74
|
+
themeColor: 'FF0000',
|
|
75
|
+
title: `🚨 Squad Watch Alert — ${hostname}`,
|
|
76
|
+
sections: [
|
|
77
|
+
{
|
|
78
|
+
activityTitle: `${failures} consecutive failures detected`,
|
|
79
|
+
facts: [
|
|
80
|
+
{ name: 'Machine', value: hostname },
|
|
81
|
+
{ name: 'Round', value: String(context.round) },
|
|
82
|
+
{ name: 'Consecutive Failures', value: String(failures) },
|
|
83
|
+
{ name: 'Timestamp', value: new Date().toISOString() },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(webhookUrl, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: { 'Content-Type': 'application/json' },
|
|
92
|
+
body: JSON.stringify(payload),
|
|
93
|
+
signal: AbortSignal.timeout(10_000),
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
summary: `Webhook POST failed: ${response.status} ${response.statusText}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
summary: `Alert sent (${failures} failures, threshold ${threshold})`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
summary: `Webhook error: ${e.message}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=webhook-alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-alerts.js","sourceRoot":"","sources":["../../../../../src/cli/commands/watch/capabilities/webhook-alerts.ts"],"names":[],"mappings":"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"}
|