@imdeadpool/guardex 7.0.41 → 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.
- package/README.md +68 -13
- package/package.json +2 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/detect.js +160 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +189 -0
- package/src/agents/launch.js +240 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +143 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +343 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +305 -1
- package/src/cli/main.js +262 -132
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/keybindings.js +224 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/layout.js +224 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +76 -33
- package/src/doctor/index.js +3 -2
- package/src/finish/index.js +39 -2
- package/src/git/index.js +65 -0
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/output/index.js +1 -1
- package/src/pr-review.js +241 -0
- package/src/scaffold/index.js +19 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +120 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +126 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/templates/AGENTS.multiagent-safety.md +27 -1
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -1
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +544 -26
- package/templates/scripts/agent-branch-start.sh +89 -22
- package/templates/scripts/agent-preflight.sh +89 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -6
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- 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
|
-
|
|
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 = [
|
|
@@ -61,14 +68,34 @@ const OPTIONAL_LOCAL_COMPANION_TOOLS = [
|
|
|
61
68
|
installArgs: ['skills', 'add', 'JuliusBrussee/caveman'],
|
|
62
69
|
},
|
|
63
70
|
];
|
|
64
|
-
|
|
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
|
-
'
|
|
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',
|
|
@@ -680,6 +722,7 @@ module.exports = {
|
|
|
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,
|
package/src/doctor/index.js
CHANGED
|
@@ -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,
|
|
@@ -442,7 +443,7 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
|
|
|
442
443
|
};
|
|
443
444
|
}
|
|
444
445
|
|
|
445
|
-
const ghBin =
|
|
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 =
|
|
894
|
+
const ghBin = GH_BIN;
|
|
894
895
|
const ghAvailable =
|
|
895
896
|
originAvailable &&
|
|
896
897
|
(explicitGhBin || originRemoteLooksLikeGithub(repoRoot)) &&
|
package/src/finish/index.js
CHANGED
|
@@ -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, {
|
|
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, {
|
|
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,
|