@imdeadpool/guardex 7.0.43 → 7.1.0

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.
Files changed (63) hide show
  1. package/README.md +26 -0
  2. package/package.json +2 -1
  3. package/skills/gx-act/SKILL.md +82 -0
  4. package/src/agents/inspect.js +17 -4
  5. package/src/agents/launch.js +10 -1
  6. package/src/agents/status.js +9 -6
  7. package/src/budget/index.js +2 -1
  8. package/src/cli/args.js +52 -2
  9. package/src/cli/commands/agents.js +364 -0
  10. package/src/cli/commands/bootstrap.js +92 -0
  11. package/src/cli/commands/branch.js +127 -0
  12. package/src/cli/commands/claude.js +674 -0
  13. package/src/cli/commands/doctor.js +268 -0
  14. package/src/cli/commands/finish.js +26 -0
  15. package/src/cli/commands/mcp.js +122 -0
  16. package/src/cli/commands/misc.js +304 -0
  17. package/src/cli/commands/pr.js +439 -0
  18. package/src/cli/commands/prompt.js +92 -0
  19. package/src/cli/commands/release.js +305 -0
  20. package/src/cli/commands/report.js +244 -0
  21. package/src/cli/commands/review.js +32 -0
  22. package/src/cli/commands/setup.js +242 -0
  23. package/src/cli/commands/status.js +338 -0
  24. package/src/cli/commands/watch.js +234 -0
  25. package/src/cli/main.js +68 -3726
  26. package/src/cli/shared/repo-env.js +161 -0
  27. package/src/cli/shared/sandbox.js +417 -0
  28. package/src/cli/shared/scaffolding.js +535 -0
  29. package/src/cli/shared/toolchain-shims.js +420 -0
  30. package/src/context.js +229 -11
  31. package/src/core/runtime.js +6 -1
  32. package/src/doctor/index.js +42 -13
  33. package/src/finish/index.js +147 -5
  34. package/src/finish/preflight.js +177 -0
  35. package/src/finish/review-gate.js +182 -0
  36. package/src/git/index.js +446 -4
  37. package/src/hooks/index.js +0 -64
  38. package/src/mcp/collect.js +370 -0
  39. package/src/mcp/server.js +157 -0
  40. package/src/output/index.js +67 -1
  41. package/src/pr-review.js +23 -0
  42. package/src/pr.js +381 -0
  43. package/src/sandbox/index.js +13 -2
  44. package/src/scaffold/agent-worktree-prep.js +213 -0
  45. package/src/scaffold/index.js +108 -10
  46. package/src/speckit/index.js +226 -0
  47. package/src/terminal/index.js +1 -76
  48. package/src/terminal/tmux.js +0 -1
  49. package/src/toolchain/index.js +20 -0
  50. package/templates/AGENTS.monorepo-apps.md +26 -0
  51. package/templates/AGENTS.multiagent-safety.md +61 -347
  52. package/templates/AGENTS.multiagent-safety.min.md +11 -0
  53. package/templates/codex/skills/gx-act/SKILL.md +82 -0
  54. package/templates/githooks/pre-commit +22 -19
  55. package/templates/scripts/agent-branch-finish.sh +8 -30
  56. package/templates/scripts/agent-branch-merge.sh +4 -1
  57. package/templates/scripts/agent-branch-start.sh +88 -3
  58. package/templates/scripts/agent-preflight.sh +31 -5
  59. package/templates/scripts/agent-worktree-prune.sh +1 -1
  60. package/templates/scripts/codex-agent.sh +0 -91
  61. package/src/agents/detect.js +0 -160
  62. package/src/cockpit/keybindings.js +0 -224
  63. package/src/cockpit/layout.js +0 -224
@@ -0,0 +1,234 @@
1
+ 'use strict';
2
+
3
+ // `gx watch` — live TUI showing every agent worktree on a single screen.
4
+ // One row per branch: last commit (age + short message), uncommitted file
5
+ // count, dev server port from .env.local, optional PR status (when `gh` is
6
+ // available). Uses the terminal's alternate screen buffer so the regular
7
+ // scrollback survives. SIGINT restores it cleanly.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { spawnSync } = require('child_process');
12
+ const { resolveRepoRoot } = require('../../git');
13
+
14
+ const ALT_SCREEN_ON = '\x1b[?1049h';
15
+ const ALT_SCREEN_OFF = '\x1b[?1049l';
16
+ const CURSOR_HIDE = '\x1b[?25l';
17
+ const CURSOR_SHOW = '\x1b[?25h';
18
+ const CLEAR_HOME = '\x1b[2J\x1b[H';
19
+
20
+ function dim(s) { return `\x1b[2m${s}\x1b[22m`; }
21
+ function bold(s) { return `\x1b[1m${s}\x1b[22m`; }
22
+ function green(s) { return `\x1b[32m${s}\x1b[39m`; }
23
+ function yellow(s) { return `\x1b[33m${s}\x1b[39m`; }
24
+ function red(s) { return `\x1b[31m${s}\x1b[39m`; }
25
+ function cyan(s) { return `\x1b[36m${s}\x1b[39m`; }
26
+
27
+ function parseWatchArgs(rawArgs) {
28
+ const options = { target: process.cwd(), intervalMs: 2000, once: false };
29
+ for (let i = 0; i < rawArgs.length; i += 1) {
30
+ const arg = rawArgs[i];
31
+ if (arg === '--target') {
32
+ options.target = rawArgs[i + 1];
33
+ i += 1;
34
+ } else if (arg === '--interval') {
35
+ const n = Number(rawArgs[i + 1]);
36
+ if (Number.isFinite(n) && n >= 0.5) options.intervalMs = Math.round(n * 1000);
37
+ i += 1;
38
+ } else if (arg === '--once') {
39
+ options.once = true;
40
+ } else if (arg === '--help' || arg === '-h') {
41
+ options.help = true;
42
+ }
43
+ }
44
+ return options;
45
+ }
46
+
47
+ function gitCapture(repoRoot, args, timeoutMs = 4000) {
48
+ const r = spawnSync('git', ['-C', repoRoot, ...args], {
49
+ stdio: ['ignore', 'pipe', 'pipe'],
50
+ timeout: timeoutMs,
51
+ });
52
+ if (r.status !== 0) return null;
53
+ return (r.stdout || '').toString();
54
+ }
55
+
56
+ function listAgentWorktrees(repoRoot) {
57
+ const out = gitCapture(repoRoot, ['worktree', 'list', '--porcelain']);
58
+ if (!out) return [];
59
+ const entries = [];
60
+ let current = {};
61
+ for (const line of out.split('\n')) {
62
+ if (line.startsWith('worktree ')) {
63
+ if (current.path) entries.push(current);
64
+ current = { path: line.slice('worktree '.length).trim() };
65
+ } else if (line.startsWith('branch ')) {
66
+ current.branch = line.slice('branch '.length).trim().replace(/^refs\/heads\//, '');
67
+ } else if (line.startsWith('HEAD ')) {
68
+ current.head = line.slice('HEAD '.length).trim();
69
+ }
70
+ }
71
+ if (current.path) entries.push(current);
72
+ return entries.filter((e) => e.branch && e.branch.startsWith('agent/'));
73
+ }
74
+
75
+ function lastCommit(worktreePath) {
76
+ const out = gitCapture(worktreePath, ['log', '-1', '--format=%h%x09%cr%x09%s']);
77
+ if (!out) return null;
78
+ const [sha, age, ...rest] = out.trim().split('\t');
79
+ return { sha, age, subject: rest.join('\t') };
80
+ }
81
+
82
+ function dirtyCount(worktreePath) {
83
+ const out = gitCapture(worktreePath, ['status', '--porcelain']);
84
+ if (out === null) return null;
85
+ return out.split('\n').filter(Boolean).length;
86
+ }
87
+
88
+ function readPortFromEnvLocal(worktreePath) {
89
+ const ports = [];
90
+ const appsRoot = path.join(worktreePath, 'apps');
91
+ let entries;
92
+ try {
93
+ entries = fs.readdirSync(appsRoot, { withFileTypes: true });
94
+ } catch {
95
+ return ports;
96
+ }
97
+ for (const e of entries) {
98
+ if (!e.isDirectory()) continue;
99
+ const envLocal = path.join(appsRoot, e.name, '.env.local');
100
+ let content;
101
+ try {
102
+ content = fs.readFileSync(envLocal, 'utf8');
103
+ } catch {
104
+ continue;
105
+ }
106
+ const m = content.match(/^PORT=(\d+)/m);
107
+ if (m) ports.push({ app: e.name, port: Number(m[1]) });
108
+ }
109
+ return ports;
110
+ }
111
+
112
+ function ghPrStatus(repoRoot, branch) {
113
+ const r = spawnSync(
114
+ 'gh',
115
+ ['pr', 'list', '--head', branch, '--state', 'all', '--limit', '1', '--json', 'number,state,url'],
116
+ { cwd: repoRoot, stdio: ['ignore', 'pipe', 'pipe'], timeout: 3000 },
117
+ );
118
+ if (r.error || r.status !== 0) return null;
119
+ try {
120
+ const arr = JSON.parse((r.stdout || '').toString());
121
+ return arr[0] || null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ function paintStatus(state) {
128
+ const upper = state ? state.toUpperCase() : '';
129
+ if (upper === 'OPEN') return green('OPEN');
130
+ if (upper === 'MERGED') return cyan('MERGED');
131
+ if (upper === 'CLOSED') return dim('CLOSED');
132
+ return dim('—');
133
+ }
134
+
135
+ function render(repoRoot, hasGh) {
136
+ const lines = [];
137
+ const now = new Date().toLocaleTimeString();
138
+ lines.push(
139
+ bold('gx watch ') +
140
+ dim(`· ${path.basename(repoRoot)} · refreshed ${now}`),
141
+ );
142
+ lines.push(dim('─'.repeat(78)));
143
+
144
+ const worktrees = listAgentWorktrees(repoRoot);
145
+ if (worktrees.length === 0) {
146
+ lines.push(dim(' (no agent/* worktrees — use `gx pivot` or `gx branch start` to spawn one)'));
147
+ lines.push('');
148
+ lines.push(dim('Press Ctrl+C to exit'));
149
+ return lines.join('\n');
150
+ }
151
+
152
+ for (const wt of worktrees) {
153
+ const commit = lastCommit(wt.path) || { sha: '—', age: '—', subject: '(no commits)' };
154
+ const dirty = dirtyCount(wt.path);
155
+ const ports = readPortFromEnvLocal(wt.path);
156
+ const pr = hasGh ? ghPrStatus(repoRoot, wt.branch) : null;
157
+ const dirtyTag = dirty == null
158
+ ? dim('—')
159
+ : dirty === 0
160
+ ? green('clean')
161
+ : yellow(`${dirty} dirty`);
162
+ const prTag = hasGh
163
+ ? (pr ? `${paintStatus(pr.state)} #${pr.number}` : dim('no PR'))
164
+ : dim('gh n/a');
165
+ const portsTag = ports.length
166
+ ? ports.map((p) => `${p.app}:${cyan(String(p.port))}`).join(' · ')
167
+ : dim('no port');
168
+
169
+ lines.push(bold(wt.branch));
170
+ lines.push(
171
+ ` ${cyan(commit.sha)} ${dim(commit.age)} — ${commit.subject.slice(0, 60)}`,
172
+ );
173
+ lines.push(
174
+ ` ${dirtyTag} · ${portsTag} · ${prTag}`,
175
+ );
176
+ lines.push(dim(` ${wt.path}`));
177
+ lines.push('');
178
+ }
179
+
180
+ lines.push(dim('Press Ctrl+C to exit'));
181
+ return lines.join('\n');
182
+ }
183
+
184
+ function detectGh() {
185
+ const r = spawnSync('gh', ['--version'], { stdio: 'ignore', timeout: 1500 });
186
+ return !r.error && r.status === 0;
187
+ }
188
+
189
+ function printHelp() {
190
+ console.log(`gx watch — live dashboard of agent worktrees
191
+
192
+ Usage:
193
+ gx watch [--interval <seconds>] [--target <repo>] [--once]
194
+
195
+ Options:
196
+ --interval N Refresh interval in seconds (default 2)
197
+ --target PATH Repo root (default: current dir)
198
+ --once Render once and exit (good for scripting)
199
+ `);
200
+ }
201
+
202
+ function watch(rawArgs) {
203
+ const options = parseWatchArgs(rawArgs);
204
+ if (options.help) {
205
+ printHelp();
206
+ return;
207
+ }
208
+ const repoRoot = resolveRepoRoot(options.target);
209
+ const hasGh = detectGh();
210
+
211
+ if (options.once) {
212
+ process.stdout.write(render(repoRoot, hasGh) + '\n');
213
+ return;
214
+ }
215
+
216
+ process.stdout.write(ALT_SCREEN_ON + CURSOR_HIDE);
217
+ const restore = () => {
218
+ process.stdout.write(CURSOR_SHOW + ALT_SCREEN_OFF);
219
+ };
220
+ const onExit = () => { restore(); process.exit(0); };
221
+ process.on('SIGINT', onExit);
222
+ process.on('SIGTERM', onExit);
223
+ process.on('exit', restore);
224
+
225
+ const tick = () => {
226
+ process.stdout.write(CLEAR_HOME + render(repoRoot, hasGh));
227
+ };
228
+ tick();
229
+ const id = setInterval(tick, options.intervalMs);
230
+ // Keep the process alive; clearInterval happens via SIGINT only.
231
+ void id;
232
+ }
233
+
234
+ module.exports = { watch, parseWatchArgs };