@imdeadpool/guardex 7.0.39 → 7.0.43

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 (85) hide show
  1. package/README.md +85 -16
  2. package/package.json +2 -1
  3. package/skills/gitguardex/SKILL.md +13 -0
  4. package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
  5. package/src/agents/cleanup-sessions.js +126 -0
  6. package/src/agents/detect.js +160 -0
  7. package/src/agents/finish.js +172 -0
  8. package/src/agents/inspect.js +189 -0
  9. package/src/agents/launch.js +240 -0
  10. package/src/agents/registry.js +133 -0
  11. package/src/agents/selection-panel.js +571 -0
  12. package/src/agents/sessions.js +151 -0
  13. package/src/agents/start.js +591 -0
  14. package/src/agents/status.js +143 -0
  15. package/src/agents/terminal.js +152 -0
  16. package/src/budget/index.js +343 -0
  17. package/src/ci-init/index.js +265 -0
  18. package/src/cli/args.js +305 -1
  19. package/src/cli/main.js +262 -132
  20. package/src/cockpit/action-runner.js +3 -0
  21. package/src/cockpit/actions.js +80 -0
  22. package/src/cockpit/control.js +1121 -0
  23. package/src/cockpit/index.js +426 -0
  24. package/src/cockpit/keybindings.js +224 -0
  25. package/src/cockpit/kitty-layout.js +549 -0
  26. package/src/cockpit/kitty-tree.js +144 -0
  27. package/src/cockpit/layout.js +224 -0
  28. package/src/cockpit/logs-reader.js +182 -0
  29. package/src/cockpit/menu.js +204 -0
  30. package/src/cockpit/pane-actions.js +597 -0
  31. package/src/cockpit/pane-menu.js +387 -0
  32. package/src/cockpit/projects-finder.js +178 -0
  33. package/src/cockpit/render.js +215 -0
  34. package/src/cockpit/settings-render.js +128 -0
  35. package/src/cockpit/settings.js +124 -0
  36. package/src/cockpit/shortcuts.js +24 -0
  37. package/src/cockpit/sidebar.js +311 -0
  38. package/src/cockpit/state.js +72 -0
  39. package/src/cockpit/theme.js +128 -0
  40. package/src/cockpit/welcome.js +266 -0
  41. package/src/context.js +78 -35
  42. package/src/doctor/index.js +4 -3
  43. package/src/finish/index.js +39 -2
  44. package/src/git/index.js +65 -0
  45. package/src/kitty/command.js +101 -0
  46. package/src/kitty/runtime.js +250 -0
  47. package/src/output/index.js +1 -1
  48. package/src/pr-review.js +241 -0
  49. package/src/scaffold/index.js +19 -0
  50. package/src/submodule/index.js +288 -0
  51. package/src/terminal/index.js +120 -0
  52. package/src/terminal/kitty.js +622 -0
  53. package/src/terminal/tmux.js +126 -0
  54. package/src/tmux/command.js +27 -0
  55. package/src/tmux/session.js +89 -0
  56. package/templates/AGENTS.multiagent-safety.md +421 -37
  57. package/templates/codex/skills/gitguardex/SKILL.md +2 -0
  58. package/templates/githooks/pre-commit +22 -1
  59. package/templates/github/workflows/README.md +87 -0
  60. package/templates/github/workflows/ci-full.yml +55 -0
  61. package/templates/github/workflows/ci.yml +56 -0
  62. package/templates/github/workflows/cr.yml +20 -1
  63. package/templates/scripts/agent-branch-finish.sh +545 -27
  64. package/templates/scripts/agent-branch-start.sh +193 -21
  65. package/templates/scripts/agent-preflight.sh +89 -0
  66. package/templates/scripts/agent-worktree-prune.sh +96 -5
  67. package/templates/scripts/codex-agent.sh +41 -6
  68. package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
  69. package/templates/scripts/review-bot-watch.sh +31 -2
  70. package/templates/scripts/agent-session-state.js +0 -171
  71. package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
  72. package/templates/vscode/guardex-active-agents/README.md +0 -34
  73. package/templates/vscode/guardex-active-agents/extension.js +0 -3782
  74. package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
  75. package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
  76. package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
  77. package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
  78. package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
  79. package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
  80. package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
  81. package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
  82. package/templates/vscode/guardex-active-agents/icon.png +0 -0
  83. package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
  84. package/templates/vscode/guardex-active-agents/package.json +0 -169
  85. package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
@@ -0,0 +1,266 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { WELCOME_SHORTCUTS } = require('./shortcuts');
5
+ const { colorize, getCockpitTheme } = require('./theme');
6
+
7
+ const DEFAULT_WIDTH = 76;
8
+ const MIN_WIDTH = 48;
9
+ const MAX_WIDTH = 88;
10
+
11
+ const DEFAULT_AGENTS = ['codex', 'claude', 'opencode', 'cursor', 'gemini'];
12
+ const GITGUARDEX_BRAND = [
13
+ ' ____ _ _ _ ___ ___ _____ __',
14
+ ' / ___|| | | | / \\ | _ \\ | _ \\ | ____|\\ \\/ /',
15
+ '| | _ | | | |/ _ \\| |_) || | | || _| \\ /',
16
+ '| |_| || |_| / ___ \\ _ < | |_| || |___ / \\',
17
+ ' \\____| \\___/_/ \\_\\_| \\_\\____/ |_____|/_/\\_\\',
18
+ ];
19
+ const GITGUARDEX_STRAPLINE = 'guarded multi-agent cockpit';
20
+
21
+ function stringValue(value, fallback = '') {
22
+ if (typeof value === 'string') {
23
+ return value.trim() || fallback;
24
+ }
25
+ if (value === null || value === undefined) {
26
+ return fallback;
27
+ }
28
+ return String(value).trim() || fallback;
29
+ }
30
+
31
+ function firstString(...values) {
32
+ for (const value of values) {
33
+ const text = stringValue(value);
34
+ if (text) {
35
+ return text;
36
+ }
37
+ }
38
+ return '';
39
+ }
40
+
41
+ function boundedWidth(settings = {}) {
42
+ const width = Number(settings.width || settings.welcomeWidth || settings.cockpitWidth);
43
+ if (!Number.isFinite(width)) {
44
+ return DEFAULT_WIDTH;
45
+ }
46
+ return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, Math.floor(width)));
47
+ }
48
+
49
+ function truncate(value, width) {
50
+ const text = stringValue(value);
51
+ if (width <= 0) {
52
+ return '';
53
+ }
54
+ if (text.length <= width) {
55
+ return text;
56
+ }
57
+ if (width <= 3) {
58
+ return text.slice(0, width);
59
+ }
60
+ return `${text.slice(0, width - 3)}...`;
61
+ }
62
+
63
+ function repoName(state = {}, settings = {}) {
64
+ const explicit = firstString(
65
+ settings.repoName,
66
+ state.repoName,
67
+ state.projectName,
68
+ state.repo,
69
+ state.name,
70
+ );
71
+ if (explicit) {
72
+ return explicit;
73
+ }
74
+
75
+ const repoPath = firstString(state.repoPath, state.repoRoot, state.agentsStatus && state.agentsStatus.repoRoot);
76
+ if (!repoPath) {
77
+ return '-';
78
+ }
79
+ return path.basename(repoPath) || repoPath;
80
+ }
81
+
82
+ function currentBranch(state = {}) {
83
+ return firstString(
84
+ state.currentBranch,
85
+ state.branch,
86
+ state.git && state.git.currentBranch,
87
+ state.agentsStatus && state.agentsStatus.currentBranch,
88
+ ) || '-';
89
+ }
90
+
91
+ function baseBranch(state = {}, settings = {}) {
92
+ return firstString(
93
+ state.baseBranch,
94
+ state.base,
95
+ settings.baseBranch,
96
+ settings.defaultBase,
97
+ state.git && state.git.baseBranch,
98
+ state.agentsStatus && state.agentsStatus.baseBranch,
99
+ ) || '-';
100
+ }
101
+
102
+ function hooksStatus(state = {}) {
103
+ const hooks = state.hooks || state.gitHooks || state.safetyHooks;
104
+ const direct = firstString(
105
+ state.hooksStatus,
106
+ state.hookStatus,
107
+ state.coreHooksPath,
108
+ state.safety && state.safety.hooksStatus,
109
+ );
110
+ if (direct) {
111
+ return direct;
112
+ }
113
+ if (typeof hooks === 'boolean') {
114
+ return hooks ? 'enabled' : 'disabled';
115
+ }
116
+ if (typeof hooks === 'string') {
117
+ return hooks.trim();
118
+ }
119
+ if (hooks && typeof hooks === 'object') {
120
+ return firstString(hooks.status, hooks.state, hooks.coreHooksPath, hooks.path, hooks.value);
121
+ }
122
+ return '';
123
+ }
124
+
125
+ function safetyStatus(state = {}) {
126
+ return firstString(
127
+ state.safetyStatus,
128
+ state.guardStatus,
129
+ state.guardexStatus,
130
+ state.safety && state.safety.status,
131
+ state.agentsStatus && state.agentsStatus.safetyStatus,
132
+ ) || 'unknown';
133
+ }
134
+
135
+ function normalizeAgentList(value) {
136
+ if (typeof value === 'string') {
137
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
138
+ }
139
+ if (!Array.isArray(value)) {
140
+ return [];
141
+ }
142
+ return value
143
+ .map((agent) => {
144
+ if (typeof agent === 'string') {
145
+ return agent.trim();
146
+ }
147
+ if (agent && typeof agent === 'object') {
148
+ return firstString(agent.name, agent.agent, agent.id, agent.label);
149
+ }
150
+ return '';
151
+ })
152
+ .filter(Boolean);
153
+ }
154
+
155
+ function availableAgents(state = {}, settings = {}) {
156
+ const agents = [
157
+ ...normalizeAgentList(settings.availableAgents),
158
+ ...normalizeAgentList(settings.agents),
159
+ ...normalizeAgentList(state.availableAgents),
160
+ ...normalizeAgentList(state.agents),
161
+ ];
162
+
163
+ const source = agents.length > 0 ? agents : DEFAULT_AGENTS;
164
+ return Array.from(new Set(source)).join(', ');
165
+ }
166
+
167
+ function totalLockCount(state = {}) {
168
+ if (Number.isFinite(state.lockCount)) {
169
+ return Math.max(0, Math.floor(state.lockCount));
170
+ }
171
+ if (Array.isArray(state.locks)) {
172
+ return state.locks.length;
173
+ }
174
+ if (state.lockSummary && Number.isFinite(state.lockSummary.count)) {
175
+ return Math.max(0, Math.floor(state.lockSummary.count));
176
+ }
177
+ if (state.agentsStatus && Number.isFinite(state.agentsStatus.lockCount)) {
178
+ return Math.max(0, Math.floor(state.agentsStatus.lockCount));
179
+ }
180
+
181
+ const sessions = Array.isArray(state.sessions) ? state.sessions : [];
182
+ return sessions.reduce((count, session) => {
183
+ if (Array.isArray(session.locks)) {
184
+ return count + session.locks.length;
185
+ }
186
+ if (Number.isFinite(session.lockCount)) {
187
+ return count + Math.max(0, Math.floor(session.lockCount));
188
+ }
189
+ return count;
190
+ }, 0);
191
+ }
192
+
193
+ function row(label, value) {
194
+ return `${label.padEnd(12)} ${value}`;
195
+ }
196
+
197
+ function boxedLine(value, width) {
198
+ const innerWidth = width - 4;
199
+ const text = truncate(value, innerWidth);
200
+ return `| ${text.padEnd(innerWidth)} |`;
201
+ }
202
+
203
+ function divider(width) {
204
+ return `+${'-'.repeat(width - 2)}+`;
205
+ }
206
+
207
+ function emptyLine(width) {
208
+ return boxedLine('', width);
209
+ }
210
+
211
+ function themedBoxedLine(value, width, token, theme) {
212
+ return colorize(boxedLine(value, width), token, theme);
213
+ }
214
+
215
+ function renderWelcomePage(state = {}, settings = {}) {
216
+ const width = boundedWidth(settings);
217
+ const theme = getCockpitTheme(settings.theme || state.theme, settings);
218
+ const hooks = hooksStatus(state);
219
+ const lines = [
220
+ colorize(divider(width), 'border', theme),
221
+ themedBoxedLine('gitguardex | gx cockpit', width, 'title', theme),
222
+ themedBoxedLine('Guardian cockpit ready. No active agent lanes.', width, 'secondary', theme),
223
+ emptyLine(width),
224
+ ];
225
+
226
+ GITGUARDEX_BRAND.forEach((brandLine) => {
227
+ lines.push(themedBoxedLine(brandLine, width, 'accent', theme));
228
+ });
229
+ lines.push(
230
+ emptyLine(width),
231
+ themedBoxedLine(GITGUARDEX_STRAPLINE, width, 'heading', theme),
232
+ );
233
+
234
+ lines.push(
235
+ emptyLine(width),
236
+ themedBoxedLine(row('Repo:', repoName(state, settings)), width, 'secondary', theme),
237
+ themedBoxedLine(row('Branch:', `${currentBranch(state)} (base ${baseBranch(state, settings)})`), width, 'secondary', theme),
238
+ themedBoxedLine(row('Safety:', safetyStatus(state)), width, 'success', theme),
239
+ );
240
+
241
+ if (hooks) {
242
+ lines.push(themedBoxedLine(row('Hooks:', hooks), width, 'secondary', theme));
243
+ }
244
+
245
+ lines.push(
246
+ themedBoxedLine(row('Locks:', String(totalLockCount(state))), width, 'accent', theme),
247
+ themedBoxedLine(row('Agents:', availableAgents(state, settings)), width, 'secondary', theme),
248
+ emptyLine(width),
249
+ themedBoxedLine('Shortcuts', width, 'heading', theme),
250
+ ...WELCOME_SHORTCUTS.map(([key, label]) => themedBoxedLine(` ${key} ${label}`, width, 'secondary', theme)),
251
+ emptyLine(width),
252
+ themedBoxedLine('Next actions', width, 'heading', theme),
253
+ themedBoxedLine(' n new agent - start a guarded agent lane', width, 'secondary', theme),
254
+ themedBoxedLine(' t terminal - open a repo terminal', width, 'secondary', theme),
255
+ themedBoxedLine(' l logs - tail apps/logs/*.log and lane events', width, 'secondary', theme),
256
+ themedBoxedLine(' p projects - switch repo (no lane selected)', width, 'secondary', theme),
257
+ themedBoxedLine(' s settings - tune cockpit defaults', width, 'secondary', theme),
258
+ colorize(divider(width), 'border', theme),
259
+ );
260
+
261
+ return `${lines.join('\n')}\n`;
262
+ }
263
+
264
+ module.exports = {
265
+ renderWelcomePage,
266
+ };
package/src/context.js CHANGED
@@ -21,7 +21,14 @@ const GLOBAL_INSTALL_COMMAND = `npm i -g ${packageJson.name}`;
21
21
  const OPENSPEC_PACKAGE = '@fission-ai/openspec';
22
22
  const OMC_PACKAGE = 'oh-my-claude-sisyphus';
23
23
  const OMC_REPO_URL = 'https://github.com/Yeachan-Heo/oh-my-claudecode';
24
- const CAVEMEM_PACKAGE = 'cavemem';
24
+ // Colony was published under @imdeadpool/colony-cli historically; the new
25
+ // canonical npm name is `colonyq`. The companion-install prompt that
26
+ // gx status / gx setup show now reads `npm i -g colonyq`. Post-install
27
+ // setup the user runs themselves (gitguardex only owns the `npm i -g` step):
28
+ // colony install --ide codex
29
+ // npx skills add recodeee/colony/skills/colony-mcp
30
+ // colony health
31
+ const COLONY_PACKAGE = 'colonyq';
25
32
  const NPX_BIN = process.env.GUARDEX_NPX_BIN || 'npx';
26
33
  const GUARDEX_HOME_DIR = path.resolve(process.env.GUARDEX_HOME_DIR || os.homedir());
27
34
  const GLOBAL_TOOLCHAIN_SERVICES = [
@@ -32,7 +39,7 @@ const GLOBAL_TOOLCHAIN_SERVICES = [
32
39
  dependencyUrl: OMC_REPO_URL,
33
40
  },
34
41
  { name: OPENSPEC_PACKAGE, packageName: OPENSPEC_PACKAGE },
35
- { name: CAVEMEM_PACKAGE, packageName: CAVEMEM_PACKAGE },
42
+ { name: 'colony', packageName: COLONY_PACKAGE },
36
43
  {
37
44
  name: '@imdeadpool/codex-account-switcher',
38
45
  packageName: '@imdeadpool/codex-account-switcher',
@@ -61,14 +68,34 @@ const OPTIONAL_LOCAL_COMPANION_TOOLS = [
61
68
  installArgs: ['skills', 'add', 'JuliusBrussee/caveman'],
62
69
  },
63
70
  ];
64
- const GH_BIN = process.env.GUARDEX_GH_BIN || 'gh';
71
+ function commandAvailable(command) {
72
+ const result = cp.spawnSync(command, ['--version'], { stdio: 'ignore' });
73
+ return result.status === 0;
74
+ }
75
+
76
+ function resolveGithubCliBin(env = process.env) {
77
+ const explicit = String(env.GUARDEX_GH_BIN || '').trim();
78
+ if (explicit) {
79
+ return explicit;
80
+ }
81
+ return commandAvailable('ghx') ? 'ghx' : 'gh';
82
+ }
83
+
84
+ const GH_BIN = resolveGithubCliBin();
85
+ const RTK_BIN = process.env.GUARDEX_RTK_BIN || 'rtk';
65
86
  const REQUIRED_SYSTEM_TOOLS = [
66
87
  {
67
88
  name: 'gh',
68
- displayName: 'GitHub (gh)',
89
+ displayName: GH_BIN === 'ghx' ? 'GitHub (ghx proxy)' : 'GitHub (gh)',
69
90
  command: GH_BIN,
70
91
  installHint: 'https://cli.github.com/',
71
92
  },
93
+ {
94
+ name: 'rtk',
95
+ displayName: 'RTK (rtk)',
96
+ command: RTK_BIN,
97
+ installHint: 'Install RTK and ensure `rtk` is on PATH.',
98
+ },
72
99
  ];
73
100
  const MAINTAINER_RELEASE_REPO = path.resolve(
74
101
  process.env.GUARDEX_RELEASE_REPO || PACKAGE_ROOT,
@@ -117,44 +144,40 @@ function toDestinationPath(relativeTemplatePath) {
117
144
  throw new Error(`Unsupported template path: ${relativeTemplatePath}`);
118
145
  }
119
146
 
147
+ // scripts/ ↔ templates/scripts/ layout convention (single source of truth):
148
+ //
149
+ // 1. PAIRED files (10): tracked on both sides; scripts/<file> is a symlink
150
+ // to ../templates/scripts/<file> per PR #548. See
151
+ // scripts/check-script-symlinks.sh for the exact list. CI + the
152
+ // .githooks/pre-commit shim both enforce that no symlink is ever
153
+ // replaced with a regular file. Edit only the templates/scripts/ copy;
154
+ // the symlink propagates.
155
+ //
156
+ // 2. SCAFFOLD-ONLY files (the 4 below + workflows + vscode extension):
157
+ // tracked only under templates/; scaffolded into gitignored
158
+ // scripts/<file> (or .githooks/<file>, etc.) by `gx setup`. Consumer
159
+ // repos receive a regular file copy at the destination; gitguardex
160
+ // itself receives the same copy and ignores it via the
161
+ // multiagent-safety .gitignore block. Edit only the templates/ copy.
162
+ //
163
+ // If a file you're about to add fits pattern (1), also add it to
164
+ // scripts/check-script-symlinks.sh's required_symlinks list. If it fits
165
+ // pattern (2), append the destination path to .gitignore's multiagent-
166
+ // safety block (auto-managed by syncManagedGitignoreLines below).
120
167
  const TEMPLATE_FILES = [
121
168
  'scripts/agent-session-state.js',
169
+ 'scripts/agent-preflight.sh',
122
170
  'scripts/guardex-docker-loader.sh',
123
171
  'scripts/guardex-env.sh',
124
- 'scripts/install-vscode-active-agents-extension.js',
125
172
  'github/pull.yml.example',
173
+ 'github/workflows/ci.yml',
174
+ 'github/workflows/ci-full.yml',
126
175
  'github/workflows/cr.yml',
127
- 'vscode/guardex-active-agents/package.json',
128
- 'vscode/guardex-active-agents/extension.js',
129
- 'vscode/guardex-active-agents/session-schema.js',
130
- 'vscode/guardex-active-agents/README.md',
131
- 'vscode/guardex-active-agents/icon.png',
132
- 'vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json',
133
- 'vscode/guardex-active-agents/fileicons/icons/agent.svg',
134
- 'vscode/guardex-active-agents/fileicons/icons/branch.svg',
135
- 'vscode/guardex-active-agents/fileicons/icons/config.svg',
136
- 'vscode/guardex-active-agents/fileicons/icons/hook.svg',
137
- 'vscode/guardex-active-agents/fileicons/icons/openspec.svg',
138
- 'vscode/guardex-active-agents/fileicons/icons/plan.svg',
139
- 'vscode/guardex-active-agents/fileicons/icons/spec.svg',
176
+ 'github/workflows/README.md',
140
177
  ];
141
178
 
142
179
  const PACKAGE_ROOT_SOURCE_OVERRIDES = new Set([
143
180
  'scripts/agent-session-state.js',
144
- 'scripts/install-vscode-active-agents-extension.js',
145
- 'vscode/guardex-active-agents/package.json',
146
- 'vscode/guardex-active-agents/extension.js',
147
- 'vscode/guardex-active-agents/session-schema.js',
148
- 'vscode/guardex-active-agents/README.md',
149
- 'vscode/guardex-active-agents/icon.png',
150
- 'vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json',
151
- 'vscode/guardex-active-agents/fileicons/icons/agent.svg',
152
- 'vscode/guardex-active-agents/fileicons/icons/branch.svg',
153
- 'vscode/guardex-active-agents/fileicons/icons/config.svg',
154
- 'vscode/guardex-active-agents/fileicons/icons/hook.svg',
155
- 'vscode/guardex-active-agents/fileicons/icons/openspec.svg',
156
- 'vscode/guardex-active-agents/fileicons/icons/plan.svg',
157
- 'vscode/guardex-active-agents/fileicons/icons/spec.svg',
158
181
  ]);
159
182
 
160
183
  const LEGACY_WORKFLOW_SHIM_SPECS = [
@@ -180,7 +203,6 @@ const LEGACY_MANAGED_REPO_FILES = [
180
203
  ...LEGACY_WORKFLOW_SHIMS,
181
204
  'scripts/agent-session-state.js',
182
205
  'scripts/guardex-docker-loader.sh',
183
- 'scripts/install-vscode-active-agents-extension.js',
184
206
  'scripts/guardex-env.sh',
185
207
  'scripts/install-agent-git-hooks.sh',
186
208
  '.githooks/pre-commit',
@@ -289,13 +311,13 @@ const MANAGED_REPO_SCAN_IGNORED_FOLDERS = [
289
311
  const MANAGED_GITIGNORE_PATHS = [
290
312
  '.omx/',
291
313
  '.omc/',
314
+ '.codex/',
292
315
  '!.vscode/',
293
316
  '.vscode/*',
294
317
  '!.vscode/settings.json',
295
318
  'scripts/agent-session-state.js',
296
319
  'scripts/guardex-docker-loader.sh',
297
320
  'scripts/guardex-env.sh',
298
- 'scripts/install-vscode-active-agents-extension.js',
299
321
  '.githooks',
300
322
  'oh-my-codex/',
301
323
  LOCK_FILE_RELATIVE,
@@ -345,6 +367,7 @@ const SUGGESTIBLE_COMMANDS = [
345
367
  'hook',
346
368
  'migrate',
347
369
  'install-agent-skills',
370
+ 'cockpit',
348
371
  'agents',
349
372
  'merge',
350
373
  'finish',
@@ -364,6 +387,8 @@ const SUGGESTIBLE_COMMANDS = [
364
387
  'copy-commands',
365
388
  'print-agents-snippet',
366
389
  'release',
390
+ 'budget',
391
+ 'ci-init',
367
392
  ];
368
393
  // CLI_COMMAND_GROUPS is the grouped source of truth the `gx --help` /
369
394
  // `gx` no-args renderer uses. Each group is ordered roughly by how often a
@@ -409,6 +434,8 @@ const CLI_COMMAND_GROUPS = [
409
434
  description: 'Review / cleanup bots, AI setup prompts, and safety reports.',
410
435
  commands: [
411
436
  ['agents', 'Start/stop repo-scoped review + cleanup bots'],
437
+ ['pr-review', 'Run local Codex/Claude PR review and post inline GitHub comments or write an artifact'],
438
+ ['cockpit', 'Create or attach to a repo tmux cockpit session'],
412
439
  ['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
413
440
  ['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
414
441
  ['report', 'Security/safety reports (e.g. OpenSSF scorecard, session severity)'],
@@ -449,6 +476,9 @@ const DOCTOR_AUTO_FINISH_MESSAGE_MAX = 160;
449
476
  const AI_SETUP_PART_ALIASES = new Map([
450
477
  ['task', 'task-loop'],
451
478
  ['loop', 'task-loop'],
479
+ ['compact-commands', 'rtk'],
480
+ ['command-compression', 'rtk'],
481
+ ['token-commands', 'rtk'],
452
482
  ['reviewbot', 'review-bot'],
453
483
  ['forksync', 'fork-sync'],
454
484
  ]);
@@ -500,6 +530,18 @@ const AI_SETUP_PARTS = [
500
530
  'gx locks claim --branch "<agent-branch>" <file...>',
501
531
  ],
502
532
  },
533
+ {
534
+ name: 'rtk',
535
+ label: 'RTK command compression',
536
+ promptLines: [
537
+ 'Prefer RTK wrappers for noisy shell discovery and verification when `rtk` is available; fall back to raw commands when missing.',
538
+ 'Files: `rtk ls .`, `rtk read <file>`, `rtk read <file> -l aggressive`, `rtk smart <file>`, `rtk find "<glob>" .`, `rtk grep "<pattern>" .`, `rtk diff <a> <b>`.',
539
+ 'Git/GitHub: `rtk git status`, `rtk git diff`, `rtk git log -n 10`, `rtk gh pr list`, `rtk gh pr view <id>`.',
540
+ 'Tests/build: `rtk test <cmd>`, `rtk err <cmd>`, `rtk jest`, `rtk vitest`, `rtk playwright test`, `rtk pytest`, `rtk cargo test`, `rtk tsc`, `rtk lint`.',
541
+ 'Use `rtk gain`, `rtk discover`, and `rtk session` to audit savings; use `rtk proxy <command>` only when raw passthrough is required.',
542
+ 'Do not wrap machine-readable commands with RTK when code parses stdout (`--porcelain`, `--json`, NUL-delimited output, or exact stdout contracts).',
543
+ ],
544
+ },
503
545
  {
504
546
  name: 'integrate',
505
547
  label: 'Integrate',
@@ -674,12 +716,13 @@ module.exports = {
674
716
  OPENSPEC_PACKAGE,
675
717
  OMC_PACKAGE,
676
718
  OMC_REPO_URL,
677
- CAVEMEM_PACKAGE,
719
+ COLONY_PACKAGE,
678
720
  NPX_BIN,
679
721
  GUARDEX_HOME_DIR,
680
722
  GLOBAL_TOOLCHAIN_SERVICES,
681
723
  GLOBAL_TOOLCHAIN_PACKAGES,
682
724
  OPTIONAL_LOCAL_COMPANION_TOOLS,
725
+ resolveGithubCliBin,
683
726
  GH_BIN,
684
727
  REQUIRED_SYSTEM_TOOLS,
685
728
  MAINTAINER_RELEASE_REPO,
@@ -3,6 +3,7 @@ const {
3
3
  path,
4
4
  TOOL_NAME,
5
5
  SHORT_TOOL_NAME,
6
+ GH_BIN,
6
7
  LOCK_FILE_RELATIVE,
7
8
  REQUIRED_MANAGED_REPO_FILES,
8
9
  OMX_SCAFFOLD_DIRECTORIES,
@@ -310,7 +311,7 @@ function extractAgentBranchFinishPrUrl(output) {
310
311
  function doctorFinishFlowIsPending(output) {
311
312
  return (
312
313
  /\[agent-branch-finish\] PR merge not completed yet; leaving PR open\./.test(output) ||
313
- /\[agent-branch-finish\] Merge pending review\/check policy\. Branch cleanup skipped for now\./.test(output) ||
314
+ /\[agent-branch-finish\] PR pending review\/check policy\./.test(output) ||
314
315
  /\[agent-branch-finish\] PR auto-merge enabled; waiting for required checks\/reviews\./.test(output)
315
316
  );
316
317
  }
@@ -442,7 +443,7 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
442
443
  };
443
444
  }
444
445
 
445
- const ghBin = process.env.GUARDEX_GH_BIN || 'gh';
446
+ const ghBin = GH_BIN;
446
447
  if (!isCommandAvailable(ghBin)) {
447
448
  return {
448
449
  status: 'skipped',
@@ -890,7 +891,7 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
890
891
 
891
892
  const originAvailable = hasOriginRemote(repoRoot);
892
893
  const explicitGhBin = Boolean(String(process.env.GUARDEX_GH_BIN || '').trim());
893
- const ghBin = process.env.GUARDEX_GH_BIN || 'gh';
894
+ const ghBin = GH_BIN;
894
895
  const ghAvailable =
895
896
  originAvailable &&
896
897
  (explicitGhBin || originRemoteLooksLikeGithub(repoRoot)) &&
@@ -26,6 +26,7 @@ const {
26
26
  parseFinishArgs,
27
27
  parseSyncArgs,
28
28
  } = require('../cli/args');
29
+ const submoduleModule = require('../submodule');
29
30
 
30
31
  function claimLocksForAutoCommit(repoRoot, worktreePath, branch) {
31
32
  const changedFiles = uniquePreserveOrder([
@@ -134,6 +135,7 @@ function autoCommitWorktreeForFinish(repoRoot, worktreePath, branch, options) {
134
135
  }
135
136
 
136
137
  function cleanup(rawArgs) {
138
+ const activeCwd = process.cwd();
137
139
  const options = parseCleanupArgs(rawArgs);
138
140
  const repoRoot = resolveRepoRoot(options.target);
139
141
 
@@ -168,7 +170,11 @@ function cleanup(rawArgs) {
168
170
  }
169
171
 
170
172
  const runCleanupCycle = () => {
171
- const runResult = runPackageAsset('worktreePrune', args, { cwd: repoRoot, stdio: 'inherit' });
173
+ const runResult = runPackageAsset('worktreePrune', args, {
174
+ cwd: repoRoot,
175
+ stdio: 'inherit',
176
+ env: { GUARDEX_PRUNE_ACTIVE_CWD: activeCwd },
177
+ });
172
178
  if (runResult.status !== 0) {
173
179
  throw new Error('Cleanup command failed');
174
180
  }
@@ -234,6 +240,7 @@ function merge(rawArgs) {
234
240
  }
235
241
 
236
242
  function finish(rawArgs, defaults = {}) {
243
+ const activeCwd = process.cwd();
237
244
  const options = parseFinishArgs(rawArgs, defaults);
238
245
  const repoRoot = resolveRepoRoot(options.target);
239
246
 
@@ -299,6 +306,32 @@ function finish(rawArgs, defaults = {}) {
299
306
  console.log(`[${TOOL_NAME}] [dry-run] Would auto-commit pending changes on '${branch}'.`);
300
307
  }
301
308
 
309
+ if (options.advanceSubmodules && worktreePath) {
310
+ const gitmodulesPath = path.join(worktreePath, '.gitmodules');
311
+ if (fs.existsSync(gitmodulesPath)) {
312
+ if (options.dryRun) {
313
+ const preview = submoduleModule.advance({
314
+ target: worktreePath,
315
+ push: false,
316
+ commit: false,
317
+ dryRun: true,
318
+ });
319
+ console.log(`[${TOOL_NAME}] [dry-run] Would advance submodules for '${branch}':`);
320
+ submoduleModule.printAdvanceResult(preview);
321
+ } else {
322
+ const advanceResult = submoduleModule.advance({
323
+ target: worktreePath,
324
+ push: false,
325
+ commit: true,
326
+ dryRun: false,
327
+ });
328
+ submoduleModule.printAdvanceResult(advanceResult);
329
+ }
330
+ } else {
331
+ console.log(`[${TOOL_NAME}] --advance-submodules ignored: '${branch}' has no .gitmodules.`);
332
+ }
333
+ }
334
+
302
335
  const finishArgs = [
303
336
  '--branch',
304
337
  branch,
@@ -325,7 +358,11 @@ function finish(rawArgs, defaults = {}) {
325
358
  continue;
326
359
  }
327
360
 
328
- const finishResult = runPackageAsset('branchFinish', finishArgs, { cwd: repoRoot, stdio: 'pipe' });
361
+ const finishResult = runPackageAsset('branchFinish', finishArgs, {
362
+ cwd: repoRoot,
363
+ stdio: 'pipe',
364
+ env: { GUARDEX_FINISH_ACTIVE_CWD: activeCwd },
365
+ });
329
366
  if (finishResult.stdout) {
330
367
  process.stdout.write(finishResult.stdout);
331
368
  }
package/src/git/index.js CHANGED
@@ -319,6 +319,70 @@ function writeProtectedBranches(repoRoot, branches) {
319
319
  gitRun(repoRoot, ['config', GIT_PROTECTED_BRANCHES_KEY, branches.join(' ')]);
320
320
  }
321
321
 
322
+ const SUBMODULE_AUTO_SYNC_CONFIGS = [
323
+ {
324
+ key: 'pull.recurseSubmodules',
325
+ value: 'true',
326
+ note: 'auto-update submodule working dirs on `git pull`',
327
+ },
328
+ {
329
+ key: 'fetch.recurseSubmodules',
330
+ value: 'on-demand',
331
+ note: 'fetch submodule commits as parent pointers move',
332
+ },
333
+ ];
334
+
335
+ function ensureSubmoduleAutoSync(repoRoot, dryRun) {
336
+ const gitmodulesPath = path.join(repoRoot, '.gitmodules');
337
+ if (!fs.existsSync(gitmodulesPath)) {
338
+ return [];
339
+ }
340
+
341
+ const operations = [];
342
+ for (const { key, value, note } of SUBMODULE_AUTO_SYNC_CONFIGS) {
343
+ const existing = readGitConfig(repoRoot, key);
344
+ if (existing) {
345
+ operations.push({
346
+ status: 'unchanged',
347
+ file: `git config ${key}`,
348
+ note: `respected pre-existing value: ${existing}`,
349
+ });
350
+ continue;
351
+ }
352
+ if (!dryRun) {
353
+ gitRun(repoRoot, ['config', key, value]);
354
+ }
355
+ operations.push({
356
+ status: dryRun ? 'would-set' : 'set',
357
+ file: `git config ${key}`,
358
+ note: `${value} (${note})`,
359
+ });
360
+ }
361
+
362
+ if (dryRun) {
363
+ operations.push({
364
+ status: 'would-sync',
365
+ file: 'git submodule update --init --recursive',
366
+ note: 'snap submodule working dirs to parent index',
367
+ });
368
+ return operations;
369
+ }
370
+
371
+ const result = gitRun(
372
+ repoRoot,
373
+ ['submodule', 'update', '--init', '--recursive'],
374
+ { allowFailure: true },
375
+ );
376
+ operations.push({
377
+ status: result.status === 0 ? 'synced' : 'failed',
378
+ file: 'git submodule update --init --recursive',
379
+ note: result.status === 0
380
+ ? 'submodule working dirs snapped to parent index'
381
+ : `failed: ${(result.stderr || '').trim().split('\n')[0] || 'unknown'}`,
382
+ });
383
+ return operations;
384
+ }
385
+
322
386
  function readGitConfig(repoRoot, key) {
323
387
  const result = gitRun(repoRoot, ['config', '--get', key], { allowFailure: true });
324
388
  if (result.status !== 0) {
@@ -697,6 +761,7 @@ module.exports = {
697
761
  hasSignificantWorkingTreeChanges,
698
762
  readProtectedBranches,
699
763
  ensureSetupProtectedBranches,
764
+ ensureSubmoduleAutoSync,
700
765
  writeProtectedBranches,
701
766
  readGitConfig,
702
767
  resolveBaseBranch,