@bradygaster/squad-cli 0.9.2-insider.5 → 0.9.3-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/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/loop.d.ts +51 -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/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/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 +108 -51
- 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/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.map +1 -1
- package/dist/cli/commands/watch/capabilities/index.js +4 -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/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/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 +18 -0
- package/dist/cli/commands/watch/config.d.ts.map +1 -1
- package/dist/cli/commands/watch/config.js +20 -1
- 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 -2
- package/dist/cli/commands/watch/index.d.ts.map +1 -1
- package/dist/cli/commands/watch/index.js +115 -8
- 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/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/core/upgrade.d.ts +18 -0
- package/dist/cli/core/upgrade.d.ts.map +1 -1
- package/dist/cli/core/upgrade.js +119 -0
- package/dist/cli/core/upgrade.js.map +1 -1
- package/dist/cli-entry.js +208 -12
- package/dist/cli-entry.js.map +1 -1
- package/package.json +5 -1
- package/templates/ceremonies.md +28 -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/squad.agent.md.template +9 -7
- package/templates/workflows/squad-triage.yml +4 -2
- package/templates/workflows/sync-squad-labels.yml +3 -1
|
@@ -11,6 +11,7 @@ const DEFAULTS = {
|
|
|
11
11
|
execute: false,
|
|
12
12
|
maxConcurrent: 1,
|
|
13
13
|
timeout: 30,
|
|
14
|
+
dispatchMode: undefined,
|
|
14
15
|
capabilities: {},
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
@@ -42,11 +43,19 @@ export function loadWatchConfig(teamRoot, cliOverrides) {
|
|
|
42
43
|
timeout: cliOverrides.timeout ?? fileConfig.timeout ?? DEFAULTS.timeout,
|
|
43
44
|
copilotFlags: cliOverrides.copilotFlags ?? fileConfig.copilotFlags ?? DEFAULTS.copilotFlags,
|
|
44
45
|
agentCmd: cliOverrides.agentCmd ?? fileConfig.agentCmd ?? DEFAULTS.agentCmd,
|
|
46
|
+
dispatchMode: cliOverrides.dispatchMode ?? fileConfig.dispatchMode ?? DEFAULTS.dispatchMode,
|
|
47
|
+
logFile: cliOverrides.logFile ?? fileConfig.logFile ?? DEFAULTS.logFile,
|
|
45
48
|
capabilities: {
|
|
46
49
|
...DEFAULTS.capabilities,
|
|
47
50
|
...(fileConfig.capabilities ?? {}),
|
|
48
51
|
...(cliOverrides.capabilities ?? {}),
|
|
49
52
|
},
|
|
53
|
+
verbose: cliOverrides.verbose ?? fileConfig.verbose ?? false,
|
|
54
|
+
authUser: cliOverrides.authUser ?? fileConfig.authUser,
|
|
55
|
+
webhookUrl: cliOverrides.webhookUrl ?? fileConfig.webhookUrl,
|
|
56
|
+
alertThreshold: cliOverrides.alertThreshold ?? fileConfig.alertThreshold,
|
|
57
|
+
maxBudget: cliOverrides.maxBudget ?? fileConfig.maxBudget,
|
|
58
|
+
machineCapabilities: cliOverrides.machineCapabilities ?? fileConfig.machineCapabilities,
|
|
50
59
|
};
|
|
51
60
|
return merged;
|
|
52
61
|
}
|
|
@@ -65,9 +74,19 @@ function normalizeFileConfig(raw) {
|
|
|
65
74
|
result.copilotFlags = raw['copilotFlags'];
|
|
66
75
|
if (typeof raw['agentCmd'] === 'string')
|
|
67
76
|
result.agentCmd = raw['agentCmd'];
|
|
77
|
+
if (typeof raw['verbose'] === 'boolean')
|
|
78
|
+
result.verbose = raw['verbose'];
|
|
79
|
+
if (typeof raw['dispatchMode'] === 'string') {
|
|
80
|
+
const mode = raw['dispatchMode'];
|
|
81
|
+
if (mode === 'fleet' || mode === 'task' || mode === 'hybrid') {
|
|
82
|
+
result.dispatchMode = mode;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (typeof raw['logFile'] === 'string')
|
|
86
|
+
result.logFile = raw['logFile'];
|
|
68
87
|
// Everything else is a capability key
|
|
69
88
|
const caps = {};
|
|
70
|
-
const reserved = new Set(['interval', 'execute', 'maxConcurrent', 'timeout', 'copilotFlags', 'agentCmd']);
|
|
89
|
+
const reserved = new Set(['interval', 'execute', 'maxConcurrent', 'timeout', 'copilotFlags', 'agentCmd', 'verbose', 'dispatchMode', 'logFile']);
|
|
71
90
|
for (const [key, value] of Object.entries(raw)) {
|
|
72
91
|
if (reserved.has(key))
|
|
73
92
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/cli/commands/watch/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/cli/commands/watch/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch health check — reports status of a running watch instance.
|
|
3
|
+
*
|
|
4
|
+
* Reads the PID file (stored in OS temp dir) written at watch startup to
|
|
5
|
+
* determine whether a watch process is alive, its uptime, auth account, and
|
|
6
|
+
* whether auth has drifted since launch.
|
|
7
|
+
*/
|
|
8
|
+
/** Shape of the PID file written by runWatch at startup. */
|
|
9
|
+
export interface WatchPidInfo {
|
|
10
|
+
pid: number;
|
|
11
|
+
startedAt: string;
|
|
12
|
+
user: string;
|
|
13
|
+
interval: number;
|
|
14
|
+
capabilities: string[];
|
|
15
|
+
repo: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Path to the PID file in the OS temp directory.
|
|
19
|
+
* Uses an MD5 hash of the repo path so different clones/worktrees get
|
|
20
|
+
* unique PID files without polluting the repo (avoids accidental commits).
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPidPath(teamRoot: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Write the PID file at watch startup.
|
|
25
|
+
* The caller is responsible for registering exit handlers to clean up.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writePidFile(teamRoot: string, info: WatchPidInfo): void;
|
|
28
|
+
/** Remove the PID file (best-effort, swallows errors). */
|
|
29
|
+
export declare function removePidFile(teamRoot: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a process with the given PID is still alive.
|
|
32
|
+
* Uses `process.kill(pid, 0)` — signal 0 tests existence without killing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Run the watch health check and return a formatted status string.
|
|
37
|
+
*
|
|
38
|
+
* @param teamRoot - The repository root (directory containing `.squad/`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function getWatchHealth(teamRoot: string): string;
|
|
41
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/watch/health.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAGvE;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAWnD;AAgCD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAwDvD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch health check — reports status of a running watch instance.
|
|
3
|
+
*
|
|
4
|
+
* Reads the PID file (stored in OS temp dir) written at watch startup to
|
|
5
|
+
* determine whether a watch process is alive, its uptime, auth account, and
|
|
6
|
+
* whether auth has drifted since launch.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import crypto from 'node:crypto';
|
|
12
|
+
import { execFileSync } from 'node:child_process';
|
|
13
|
+
/**
|
|
14
|
+
* Path to the PID file in the OS temp directory.
|
|
15
|
+
* Uses an MD5 hash of the repo path so different clones/worktrees get
|
|
16
|
+
* unique PID files without polluting the repo (avoids accidental commits).
|
|
17
|
+
*/
|
|
18
|
+
export function getPidPath(teamRoot) {
|
|
19
|
+
const hash = crypto.createHash('md5').update(teamRoot).digest('hex').slice(0, 12);
|
|
20
|
+
return path.join(os.tmpdir(), `squad-watch-${hash}.json`);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Write the PID file at watch startup.
|
|
24
|
+
* The caller is responsible for registering exit handlers to clean up.
|
|
25
|
+
*/
|
|
26
|
+
export function writePidFile(teamRoot, info) {
|
|
27
|
+
const pidPath = getPidPath(teamRoot);
|
|
28
|
+
fs.writeFileSync(pidPath, JSON.stringify(info, null, 2));
|
|
29
|
+
}
|
|
30
|
+
/** Remove the PID file (best-effort, swallows errors). */
|
|
31
|
+
export function removePidFile(teamRoot) {
|
|
32
|
+
try {
|
|
33
|
+
fs.unlinkSync(getPidPath(teamRoot));
|
|
34
|
+
}
|
|
35
|
+
catch { /* already gone */ }
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if a process with the given PID is still alive.
|
|
39
|
+
* Uses `process.kill(pid, 0)` — signal 0 tests existence without killing.
|
|
40
|
+
*/
|
|
41
|
+
export function isProcessAlive(pid) {
|
|
42
|
+
try {
|
|
43
|
+
process.kill(pid, 0);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
// EPERM means the process exists but we lack permission to signal it
|
|
48
|
+
if (e && typeof e === 'object' && 'code' in e && e.code === 'EPERM') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Format a duration in milliseconds to a human-readable string. */
|
|
55
|
+
function formatUptime(ms) {
|
|
56
|
+
const totalMinutes = Math.floor(ms / 60_000);
|
|
57
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
58
|
+
const minutes = totalMinutes % 60;
|
|
59
|
+
if (hours > 0)
|
|
60
|
+
return `${hours}h ${minutes}m`;
|
|
61
|
+
return `${minutes}m`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns the currently active `gh` account, or undefined.
|
|
65
|
+
* Duplicated from index.ts to avoid circular imports — the canonical
|
|
66
|
+
* version lives in `getActiveGhUser()` in the watch index.
|
|
67
|
+
*/
|
|
68
|
+
/** Probes gh auth status — captures both stdout and stderr since gh may write to either. */
|
|
69
|
+
function probeCurrentGhUser() {
|
|
70
|
+
try {
|
|
71
|
+
const result = execFileSync('gh', ['auth', 'status', '--active'], {
|
|
72
|
+
encoding: 'utf-8',
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
});
|
|
75
|
+
const match = result.match(/account\s+(\S+)/);
|
|
76
|
+
return match?.[1];
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
const stderr = e.stderr ?? '';
|
|
80
|
+
const match = stderr.match(/account\s+(\S+)/);
|
|
81
|
+
return match?.[1];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run the watch health check and return a formatted status string.
|
|
86
|
+
*
|
|
87
|
+
* @param teamRoot - The repository root (directory containing `.squad/`).
|
|
88
|
+
*/
|
|
89
|
+
export function getWatchHealth(teamRoot) {
|
|
90
|
+
const pidPath = getPidPath(teamRoot);
|
|
91
|
+
if (!fs.existsSync(pidPath)) {
|
|
92
|
+
return '📋 No watch instance detected.\n Start one with: squad watch --execute --interval 5';
|
|
93
|
+
}
|
|
94
|
+
let info;
|
|
95
|
+
try {
|
|
96
|
+
info = JSON.parse(fs.readFileSync(pidPath, 'utf-8'));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return `⚠ Corrupt PID file — cannot read watch status.\n Delete ${pidPath} and restart.`;
|
|
100
|
+
}
|
|
101
|
+
// Validate minimal fields
|
|
102
|
+
if (typeof info.pid !== 'number') {
|
|
103
|
+
return `⚠ Invalid PID file (missing pid).\n Delete ${pidPath} and restart.`;
|
|
104
|
+
}
|
|
105
|
+
// Check if the process is actually running
|
|
106
|
+
if (!isProcessAlive(info.pid)) {
|
|
107
|
+
// Stale PID file — process died without cleanup
|
|
108
|
+
removePidFile(teamRoot);
|
|
109
|
+
return `⚠ Stale watch detected (PID ${info.pid} is dead). Cleaned up.\n Restart with: squad watch --execute --interval 5`;
|
|
110
|
+
}
|
|
111
|
+
// Process is alive — build status report
|
|
112
|
+
// Validate startedAt before computing uptime
|
|
113
|
+
if (!info.startedAt || isNaN(new Date(info.startedAt).getTime())) {
|
|
114
|
+
return `⚠ Invalid PID file (bad startedAt).
|
|
115
|
+
Delete ${pidPath} and restart.`;
|
|
116
|
+
}
|
|
117
|
+
const uptime = formatUptime(Date.now() - new Date(info.startedAt).getTime());
|
|
118
|
+
const lines = [
|
|
119
|
+
'🔄 Watch Instance — RUNNING',
|
|
120
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
121
|
+
` PID: ${info.pid}`,
|
|
122
|
+
` Uptime: ${uptime}`,
|
|
123
|
+
` Auth: ${info.user}`,
|
|
124
|
+
` Interval: ${info.interval}m`,
|
|
125
|
+
` Repo: ${info.repo}`,
|
|
126
|
+
` Capabilities: ${(info.capabilities ?? []).join(', ') || '(none)'}`,
|
|
127
|
+
];
|
|
128
|
+
// Auth drift detection — compare current gh user vs recorded
|
|
129
|
+
const currentUser = probeCurrentGhUser();
|
|
130
|
+
if (currentUser && currentUser !== info.user) {
|
|
131
|
+
lines.push(` ⚠ AUTH DRIFT: Expected ${info.user}, current is ${currentUser}`);
|
|
132
|
+
}
|
|
133
|
+
else if (currentUser) {
|
|
134
|
+
lines.push(' Auth status: ✓ matches expected');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
lines.push(' Auth status: ? could not verify');
|
|
138
|
+
}
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../../../src/cli/commands/watch/health.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ export { loadWatchConfig } from './config.js';
|
|
|
12
12
|
export type { WatchCapability, WatchContext, WatchPhase, PreflightResult, CapabilityResult } from './types.js';
|
|
13
13
|
export { CapabilityRegistry } from './registry.js';
|
|
14
14
|
export { createDefaultRegistry } from './capabilities/index.js';
|
|
15
|
+
export { createVerboseLogger, type VerboseLogger } from './verbose.js';
|
|
16
|
+
export { getWatchHealth, writePidFile, removePidFile, getPidPath, isProcessAlive, type WatchPidInfo } from './health.js';
|
|
15
17
|
/** Normalized work item for watch operations. */
|
|
16
18
|
export interface WatchWorkItem {
|
|
17
19
|
number: number;
|
|
@@ -53,7 +55,12 @@ export interface BoardState {
|
|
|
53
55
|
readyToMerge: number;
|
|
54
56
|
executed: number;
|
|
55
57
|
}
|
|
56
|
-
export
|
|
58
|
+
export interface ReportBoardOptions {
|
|
59
|
+
notifyLevel?: 'all' | 'important' | 'none';
|
|
60
|
+
machineName?: string;
|
|
61
|
+
repoName?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function reportBoard(state: BoardState, round: number, options?: ReportBoardOptions): void;
|
|
57
64
|
/**
|
|
58
65
|
* Legacy WatchOptions type — still accepted by runWatch for backward
|
|
59
66
|
* compatibility. New code should use {@link WatchConfig} instead.
|
|
@@ -75,7 +82,7 @@ export interface WatchOptions {
|
|
|
75
82
|
decisionHygiene?: boolean;
|
|
76
83
|
channelRouting?: boolean;
|
|
77
84
|
}
|
|
78
|
-
export { findExecutableIssues } from './capabilities/execute.js';
|
|
85
|
+
export { findExecutableIssues, classifyIssue } from './capabilities/execute.js';
|
|
79
86
|
export declare function buildAgentCommand(issue: WatchWorkItem, teamRoot: string, options: WatchOptions): {
|
|
80
87
|
cmd: string;
|
|
81
88
|
args: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/watch/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/watch/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* always runs — it is not an opt-in capability.
|
|
7
7
|
*/
|
|
8
8
|
import path from 'node:path';
|
|
9
|
+
import fs from 'node:fs';
|
|
9
10
|
import { execFile, execFileSync } from 'node:child_process';
|
|
10
11
|
import { promisify } from 'node:util';
|
|
11
12
|
import { FSStorageProvider } from '@bradygaster/squad-sdk';
|
|
@@ -21,9 +22,12 @@ import { ghAvailable, ghAuthenticated, ghRateLimitCheck, isRateLimitError } from
|
|
|
21
22
|
import { PredictiveCircuitBreaker, getTrafficLight, } from '@bradygaster/squad-sdk/ralph/rate-limiting';
|
|
22
23
|
import { createPlatformAdapter } from '@bradygaster/squad-sdk/platform';
|
|
23
24
|
import { createDefaultRegistry } from './capabilities/index.js';
|
|
25
|
+
import { createVerboseLogger } from './verbose.js';
|
|
24
26
|
export { loadWatchConfig } from './config.js';
|
|
25
27
|
export { CapabilityRegistry } from './registry.js';
|
|
26
28
|
export { createDefaultRegistry } from './capabilities/index.js';
|
|
29
|
+
export { createVerboseLogger } from './verbose.js';
|
|
30
|
+
export { getWatchHealth, writePidFile, removePidFile, getPidPath, isProcessAlive } from './health.js';
|
|
27
31
|
// ── SDK Mapping Helpers ──────────────────────────────────────────
|
|
28
32
|
function toWatchWorkItem(wi) {
|
|
29
33
|
return {
|
|
@@ -96,13 +100,21 @@ async function editWorkItem(adapter, id, options) {
|
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
|
-
export function reportBoard(state, round) {
|
|
103
|
+
export function reportBoard(state, round, options) {
|
|
104
|
+
const level = options?.notifyLevel ?? 'all';
|
|
100
105
|
const total = Object.values(state).reduce((a, b) => a + b, 0);
|
|
106
|
+
if (level === 'none')
|
|
107
|
+
return;
|
|
108
|
+
if (level === 'important' && total === 0)
|
|
109
|
+
return;
|
|
101
110
|
if (total === 0) {
|
|
102
111
|
console.log(`${DIM}📋 Board is clear — Ralph is idling${RESET}`);
|
|
103
112
|
return;
|
|
104
113
|
}
|
|
105
|
-
|
|
114
|
+
const suffix = options?.machineName || options?.repoName
|
|
115
|
+
? ` (${[options.machineName, options.repoName].filter(Boolean).join(' · ')})`
|
|
116
|
+
: '';
|
|
117
|
+
console.log(`\n${BOLD}🔄 Ralph — Round ${round}${suffix}${RESET}`);
|
|
106
118
|
console.log('━'.repeat(30));
|
|
107
119
|
if (state.untriaged > 0)
|
|
108
120
|
console.log(` 🔴 Untriaged: ${state.untriaged}`);
|
|
@@ -125,10 +137,14 @@ export function reportBoard(state, round) {
|
|
|
125
137
|
function emptyBoardState() {
|
|
126
138
|
return { untriaged: 0, assigned: 0, drafts: 0, needsReview: 0, changesRequested: 0, ciFailures: 0, readyToMerge: 0, executed: 0 };
|
|
127
139
|
}
|
|
128
|
-
async function checkPRs(roster, adapter) {
|
|
140
|
+
async function checkPRs(roster, adapter, vlog) {
|
|
129
141
|
const timestamp = new Date().toLocaleTimeString();
|
|
130
142
|
const prs = await listWatchPullRequests(adapter, { state: 'open', limit: 20 });
|
|
131
143
|
const squadPRs = prs.filter(pr => pr.labels.some(l => l.name.startsWith('squad')) || pr.headRefName.startsWith('squad/'));
|
|
144
|
+
vlog?.log(`PRs found: ${prs.length} total`);
|
|
145
|
+
for (const pr of squadPRs) {
|
|
146
|
+
vlog?.log(` PR #${pr.number}: "${pr.title}" draft=${pr.isDraft} review=${pr.reviewDecision ?? 'none'}`);
|
|
147
|
+
}
|
|
132
148
|
if (squadPRs.length === 0) {
|
|
133
149
|
return { drafts: 0, needsReview: 0, changesRequested: 0, ciFailures: 0, readyToMerge: 0, totalOpen: 0 };
|
|
134
150
|
}
|
|
@@ -182,10 +198,16 @@ const BLOCKED_LABELS = new Set([
|
|
|
182
198
|
'status:scheduled', 'status:needs-action', 'status:needs-decision',
|
|
183
199
|
'status:needs-review', 'pending-user', 'do-not-merge',
|
|
184
200
|
]);
|
|
185
|
-
async function runCheck(rules, modules, roster, hasCopilot, autoAssign, capabilities, adapter) {
|
|
201
|
+
async function runCheck(rules, modules, roster, hasCopilot, autoAssign, capabilities, adapter, vlog) {
|
|
186
202
|
const timestamp = new Date().toLocaleTimeString();
|
|
187
203
|
try {
|
|
188
204
|
const issues = await listWatchWorkItems(adapter, { label: 'squad', state: 'open', limit: 20 });
|
|
205
|
+
vlog?.log(`Issues found: ${issues.length} total`);
|
|
206
|
+
for (const issue of issues) {
|
|
207
|
+
const labels = issue.labels?.map((l) => l.name).join(', ') ?? 'none';
|
|
208
|
+
const assignees = issue.assignees?.map((a) => a.login).join(', ') ?? 'none';
|
|
209
|
+
vlog?.log(` #${issue.number}: "${issue.title}" [${labels}] assignees=[${assignees}]`);
|
|
210
|
+
}
|
|
189
211
|
const { filterByCapabilities } = await import('@bradygaster/squad-sdk/ralph/capabilities');
|
|
190
212
|
const { handled: capableIssues, skipped: incapableIssues } = filterByCapabilities(issues, capabilities);
|
|
191
213
|
for (const { issue, missing } of incapableIssues) {
|
|
@@ -235,7 +257,7 @@ async function runCheck(rules, modules, roster, hasCopilot, autoAssign, capabili
|
|
|
235
257
|
console.error(`${RED}✗${RESET} [${timestamp}] Failed to assign @copilot to #${issue.number}: ${e.message}`);
|
|
236
258
|
}
|
|
237
259
|
}
|
|
238
|
-
const prState = await checkPRs(roster, adapter);
|
|
260
|
+
const prState = await checkPRs(roster, adapter, vlog);
|
|
239
261
|
return { untriaged: untriaged.length, assigned: assignedIssues.length, executed: 0, ...prState };
|
|
240
262
|
}
|
|
241
263
|
catch (e) {
|
|
@@ -313,7 +335,7 @@ async function runPhase(phase, enabled, context, config) {
|
|
|
313
335
|
...context,
|
|
314
336
|
config: typeof capConfig === 'object' && capConfig !== null
|
|
315
337
|
? capConfig
|
|
316
|
-
: { enabled: !!capConfig, maxConcurrent: config.maxConcurrent, timeout: config.timeout },
|
|
338
|
+
: { enabled: !!capConfig, maxConcurrent: config.maxConcurrent, timeout: config.timeout, dispatchMode: config.dispatchMode },
|
|
317
339
|
};
|
|
318
340
|
const result = await cap.execute(capContext);
|
|
319
341
|
results.set(cap.name, result);
|
|
@@ -398,7 +420,7 @@ function legacyToConfig(options) {
|
|
|
398
420
|
};
|
|
399
421
|
}
|
|
400
422
|
// ── Exported helpers (backward compat) ───────────────────────────
|
|
401
|
-
export { findExecutableIssues } from './capabilities/execute.js';
|
|
423
|
+
export { findExecutableIssues, classifyIssue } from './capabilities/execute.js';
|
|
402
424
|
export function buildAgentCommand(issue, teamRoot, options) {
|
|
403
425
|
const prompt = `Work on issue #${issue.number}: ${issue.title}. Read the issue body for full details.`;
|
|
404
426
|
if (options.agentCmd) {
|
|
@@ -484,6 +506,31 @@ export async function runWatch(dest, options) {
|
|
|
484
506
|
catch (err) {
|
|
485
507
|
return fatal(`Could not detect platform: ${err.message}`);
|
|
486
508
|
}
|
|
509
|
+
// Verbose logger
|
|
510
|
+
const vlog = createVerboseLogger(config.verbose ?? false);
|
|
511
|
+
vlog.section('Startup');
|
|
512
|
+
vlog.table({
|
|
513
|
+
repo: teamRoot,
|
|
514
|
+
platform: adapter.type,
|
|
515
|
+
// This table only prints when verbose mode is active, so verbose is always true here.
|
|
516
|
+
verbose: config.verbose ?? false,
|
|
517
|
+
interval: `${config.interval}m`,
|
|
518
|
+
execute: config.execute ?? false,
|
|
519
|
+
agentCmd: config.agentCmd ?? '(default: gh copilot)',
|
|
520
|
+
dispatchMode: config.capabilities['wave-dispatch'] ? 'wave' : 'task',
|
|
521
|
+
maxConcurrent: config.maxConcurrent ?? 1,
|
|
522
|
+
});
|
|
523
|
+
// Auth check (verbose only — avoid unnecessary process spawn on every startup)
|
|
524
|
+
if (config.verbose && adapter.type === 'github') {
|
|
525
|
+
try {
|
|
526
|
+
const authOut = execFileSync('gh', ['auth', 'status'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
527
|
+
const activeAccount = authOut.match(/Logged in to .* account (\S+)/)?.[1];
|
|
528
|
+
vlog.log(`Auth: ${activeAccount ?? 'unknown'}`);
|
|
529
|
+
}
|
|
530
|
+
catch {
|
|
531
|
+
vlog.warn('gh auth status failed — API calls may fail silently');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
487
534
|
// Verify platform CLI availability
|
|
488
535
|
if (adapter.type === 'github') {
|
|
489
536
|
if (!(await ghAvailable()))
|
|
@@ -520,6 +567,20 @@ export async function runWatch(dest, options) {
|
|
|
520
567
|
if (roster.length === 0) {
|
|
521
568
|
fatal('No squad members found in team.md');
|
|
522
569
|
}
|
|
570
|
+
// Pre-create squad member labels so addTag never fails on missing labels
|
|
571
|
+
if (adapter.ensureTag) {
|
|
572
|
+
for (const member of roster) {
|
|
573
|
+
try {
|
|
574
|
+
await adapter.ensureTag(member.label, { color: 'd4c5f9', description: `Squad triage: ${member.name}` });
|
|
575
|
+
}
|
|
576
|
+
catch { /* best-effort — continue if label creation fails */ }
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
await adapter.ensureTag('squad:copilot', { color: 'd4c5f9', description: 'Squad triage: Copilot coding agent' });
|
|
580
|
+
}
|
|
581
|
+
catch { /* best-effort */ }
|
|
582
|
+
console.log(`${DIM}Labels: ensured ${roster.length + 1} squad labels exist${RESET}`);
|
|
583
|
+
}
|
|
523
584
|
const hasCopilot = content.includes('🤖 Coding Agent') || content.includes('@copilot');
|
|
524
585
|
const autoAssign = content.includes('<!-- copilot-auto-assign: true -->');
|
|
525
586
|
const monitorSessionId = 'ralph-watch';
|
|
@@ -545,6 +606,7 @@ export async function runWatch(dest, options) {
|
|
|
545
606
|
config: {},
|
|
546
607
|
agentCmd: config.agentCmd,
|
|
547
608
|
copilotFlags: config.copilotFlags,
|
|
609
|
+
verbose: config.verbose,
|
|
548
610
|
};
|
|
549
611
|
const enabledCapabilities = await preflightCapabilities(registry, config, baseContext);
|
|
550
612
|
// Print startup banner
|
|
@@ -558,7 +620,30 @@ export async function runWatch(dest, options) {
|
|
|
558
620
|
if (config.execute) {
|
|
559
621
|
console.log(`${DIM}Max concurrent: ${config.maxConcurrent} | Timeout: ${config.timeout}m${RESET}`);
|
|
560
622
|
}
|
|
623
|
+
// Warn when fleet dispatch mode is set but the fleet-dispatch capability is not enabled
|
|
624
|
+
if ((config.dispatchMode === 'fleet' || config.dispatchMode === 'hybrid') &&
|
|
625
|
+
!enabledCapabilities.some(c => c.name === 'fleet-dispatch')) {
|
|
626
|
+
console.warn(`${YELLOW}⚠${RESET} dispatchMode="${config.dispatchMode}" but fleet-dispatch capability is not enabled. Read-heavy issues will not be batched.`);
|
|
627
|
+
}
|
|
561
628
|
console.log();
|
|
629
|
+
// Fix 2: Set up log-file tee (after banner so the banner itself is captured too)
|
|
630
|
+
let logStream;
|
|
631
|
+
if (config.logFile) {
|
|
632
|
+
const resolvedLogFile = path.resolve(config.logFile);
|
|
633
|
+
logStream = fs.createWriteStream(resolvedLogFile, { flags: 'a' });
|
|
634
|
+
const origLog = console.log.bind(console);
|
|
635
|
+
console.log = (...args) => {
|
|
636
|
+
origLog(...args);
|
|
637
|
+
const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
638
|
+
const line = args.map(a => (typeof a === 'string' ? a : String(a))).join(' ');
|
|
639
|
+
// Strip ANSI escape codes for the file
|
|
640
|
+
const plain = line.replace(/\x1b\[[0-9;]*m/g, '');
|
|
641
|
+
logStream.write(`[${timestamp}] ${plain}\n`);
|
|
642
|
+
};
|
|
643
|
+
console.log(`${DIM}Log file: ${resolvedLogFile}${RESET}`);
|
|
644
|
+
}
|
|
645
|
+
// Fix 3: Immediate visual feedback after banner
|
|
646
|
+
console.log(`Running first check now...`);
|
|
562
647
|
// Initialize circuit breaker (#515)
|
|
563
648
|
const circuitBreaker = new PredictiveCircuitBreaker();
|
|
564
649
|
let cbState = loadCBState(squadDirInfo.path);
|
|
@@ -566,6 +651,7 @@ export async function runWatch(dest, options) {
|
|
|
566
651
|
let roundInProgress = false;
|
|
567
652
|
async function executeRound() {
|
|
568
653
|
const ts = new Date().toLocaleTimeString();
|
|
654
|
+
const roundStart = Date.now();
|
|
569
655
|
// Circuit breaker gate
|
|
570
656
|
if (cbState.status === 'open') {
|
|
571
657
|
const elapsed = Date.now() - new Date(cbState.openedAt).getTime();
|
|
@@ -606,6 +692,9 @@ export async function runWatch(dest, options) {
|
|
|
606
692
|
}
|
|
607
693
|
round++;
|
|
608
694
|
const roundContext = { ...baseContext, round };
|
|
695
|
+
// Fix 1: Print round start marker
|
|
696
|
+
console.log(`\n${DIM}Starting round ${round}...${RESET}`);
|
|
697
|
+
vlog.section(`Round ${round}`);
|
|
609
698
|
// Phase 1: pre-scan (self-pull, subsquad discovery)
|
|
610
699
|
await runPhase('pre-scan', enabledCapabilities, roundContext, config);
|
|
611
700
|
// SubSquad discovery (informational, not a capability)
|
|
@@ -614,7 +703,7 @@ export async function runWatch(dest, options) {
|
|
|
614
703
|
console.log(`${DIM}📂 Discovered ${subSquads.length} subsquad(s): ${subSquads.map(s => s.name).join(', ')}${RESET}`);
|
|
615
704
|
}
|
|
616
705
|
// Core: triage (always runs — not a capability)
|
|
617
|
-
const roundState = await runCheck(rules, modules, roster, hasCopilot, autoAssign, capabilities, adapter);
|
|
706
|
+
const roundState = await runCheck(rules, modules, roster, hasCopilot, autoAssign, capabilities, adapter, vlog);
|
|
618
707
|
// Phase 2: post-triage (two-pass hydration)
|
|
619
708
|
await runPhase('post-triage', enabledCapabilities, roundContext, config);
|
|
620
709
|
// Phase 3: post-execute (execute issues, wave dispatch, board updates)
|
|
@@ -633,7 +722,21 @@ export async function runWatch(dest, options) {
|
|
|
633
722
|
timestamp: new Date(),
|
|
634
723
|
});
|
|
635
724
|
await monitor.healthCheck();
|
|
725
|
+
// Verbose board counters
|
|
726
|
+
vlog.table({
|
|
727
|
+
untriaged: roundState.untriaged,
|
|
728
|
+
assigned: roundState.assigned,
|
|
729
|
+
drafts: roundState.drafts,
|
|
730
|
+
needsReview: roundState.needsReview,
|
|
731
|
+
changesRequested: roundState.changesRequested,
|
|
732
|
+
ciFailures: roundState.ciFailures,
|
|
733
|
+
readyToMerge: roundState.readyToMerge,
|
|
734
|
+
executed: roundState.executed,
|
|
735
|
+
});
|
|
636
736
|
reportBoard(roundState, round);
|
|
737
|
+
// Fix 1: Print next poll time
|
|
738
|
+
const nextPollTime = new Date(Date.now() + interval * 60 * 1000);
|
|
739
|
+
console.log(`${DIM}Next poll at ${nextPollTime.toLocaleTimeString()}${RESET}`);
|
|
637
740
|
// Post-round: update circuit breaker on success
|
|
638
741
|
if (cbState.status === 'half-open') {
|
|
639
742
|
cbState.consecutiveSuccesses++;
|
|
@@ -649,6 +752,9 @@ export async function runWatch(dest, options) {
|
|
|
649
752
|
cbState.consecutiveFailures = 0;
|
|
650
753
|
}
|
|
651
754
|
saveCBState(squadDirInfo.path, cbState);
|
|
755
|
+
const roundEnd = Date.now();
|
|
756
|
+
const elapsed = ((roundEnd - roundStart) / 1000).toFixed(1);
|
|
757
|
+
vlog.log(`Round ${round} complete (${elapsed}s)`);
|
|
652
758
|
}
|
|
653
759
|
// Run immediately, then on interval
|
|
654
760
|
await executeRound();
|
|
@@ -695,6 +801,7 @@ export async function runWatch(dest, options) {
|
|
|
695
801
|
await monitor.stop();
|
|
696
802
|
saveCBState(squadDirInfo.path, cbState);
|
|
697
803
|
console.log(`\n${DIM}🔄 Ralph — Watch stopped${RESET}`);
|
|
804
|
+
logStream?.end();
|
|
698
805
|
resolve();
|
|
699
806
|
};
|
|
700
807
|
process.on('SIGINT', shutdown);
|