@imdeadpool/guardex 7.0.16 → 7.0.18
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/CONTRIBUTING.md +1 -1
- package/README.md +178 -53
- package/bin/multiagent-safety.js +691 -125
- package/package.json +2 -2
- package/templates/AGENTS.multiagent-safety.md +4 -4
- package/templates/githooks/post-checkout +1 -1
- package/templates/githooks/post-merge +19 -6
- package/templates/githooks/pre-commit +8 -8
- package/templates/scripts/agent-session-state.js +110 -0
- package/templates/scripts/codex-agent.sh +77 -0
- package/templates/scripts/install-vscode-active-agents-extension.js +92 -0
- package/templates/scripts/openspec/init-change-workspace.sh +77 -9
- package/templates/scripts/openspec/init-plan-workspace.sh +576 -74
- package/templates/vscode/guardex-active-agents/README.md +21 -0
- package/templates/vscode/guardex-active-agents/extension.js +317 -0
- package/templates/vscode/guardex-active-agents/package.json +57 -0
- package/templates/vscode/guardex-active-agents/session-schema.js +407 -0
package/bin/multiagent-safety.js
CHANGED
|
@@ -5,12 +5,20 @@ const os = require('node:os');
|
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const cp = require('node:child_process');
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
9
|
+
const packageJsonPath = path.join(PACKAGE_ROOT, 'package.json');
|
|
9
10
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
10
11
|
|
|
11
12
|
const TOOL_NAME = 'gitguardex';
|
|
12
13
|
const SHORT_TOOL_NAME = 'gx';
|
|
14
|
+
if (!process.env.GUARDEX_CLI_ENTRY) {
|
|
15
|
+
process.env.GUARDEX_CLI_ENTRY = __filename;
|
|
16
|
+
}
|
|
17
|
+
if (!process.env.GUARDEX_NODE_BIN) {
|
|
18
|
+
process.env.GUARDEX_NODE_BIN = process.execPath;
|
|
19
|
+
}
|
|
13
20
|
const LEGACY_NAMES = ['guardex', 'multiagent-safety'];
|
|
21
|
+
const GLOBAL_INSTALL_COMMAND = `npm i -g ${packageJson.name}`;
|
|
14
22
|
const OPENSPEC_PACKAGE = '@fission-ai/openspec';
|
|
15
23
|
const OMC_PACKAGE = 'oh-my-claude-sisyphus';
|
|
16
24
|
const OMC_REPO_URL = 'https://github.com/Yeachan-Heo/oh-my-claudecode';
|
|
@@ -84,47 +92,67 @@ const COMPOSE_HINT_FILES = [
|
|
|
84
92
|
'compose.yaml',
|
|
85
93
|
];
|
|
86
94
|
|
|
87
|
-
const TEMPLATE_ROOT = path.
|
|
95
|
+
const TEMPLATE_ROOT = path.join(PACKAGE_ROOT, 'templates');
|
|
96
|
+
|
|
97
|
+
const HOOK_NAMES = ['pre-commit', 'pre-push', 'post-merge', 'post-checkout'];
|
|
88
98
|
|
|
89
99
|
const TEMPLATE_FILES = [
|
|
90
|
-
'scripts/agent-
|
|
91
|
-
'scripts/agent-branch-finish.sh',
|
|
92
|
-
'scripts/agent-branch-merge.sh',
|
|
93
|
-
'scripts/codex-agent.sh',
|
|
100
|
+
'scripts/agent-session-state.js',
|
|
94
101
|
'scripts/guardex-docker-loader.sh',
|
|
95
|
-
'scripts/review-bot-watch.sh',
|
|
96
|
-
'scripts/agent-worktree-prune.sh',
|
|
97
|
-
'scripts/agent-file-locks.py',
|
|
98
102
|
'scripts/guardex-env.sh',
|
|
99
|
-
'scripts/install-
|
|
100
|
-
'scripts/openspec/init-plan-workspace.sh',
|
|
101
|
-
'scripts/openspec/init-change-workspace.sh',
|
|
102
|
-
'githooks/pre-commit',
|
|
103
|
-
'githooks/pre-push',
|
|
104
|
-
'githooks/post-merge',
|
|
105
|
-
'githooks/post-checkout',
|
|
106
|
-
'codex/skills/gitguardex/SKILL.md',
|
|
107
|
-
'codex/skills/guardex-merge-skills-to-dev/SKILL.md',
|
|
108
|
-
'claude/commands/gitguardex.md',
|
|
103
|
+
'scripts/install-vscode-active-agents-extension.js',
|
|
109
104
|
'github/pull.yml.example',
|
|
110
105
|
'github/workflows/cr.yml',
|
|
106
|
+
'vscode/guardex-active-agents/package.json',
|
|
107
|
+
'vscode/guardex-active-agents/extension.js',
|
|
108
|
+
'vscode/guardex-active-agents/session-schema.js',
|
|
109
|
+
'vscode/guardex-active-agents/README.md',
|
|
111
110
|
];
|
|
112
111
|
|
|
113
|
-
const
|
|
112
|
+
const SCRIPT_SHIMS = [
|
|
113
|
+
{ relativePath: 'scripts/agent-branch-start.sh', kind: 'shell', command: ['branch', 'start'] },
|
|
114
|
+
{ relativePath: 'scripts/agent-branch-finish.sh', kind: 'shell', command: ['branch', 'finish'] },
|
|
115
|
+
{ relativePath: 'scripts/agent-branch-merge.sh', kind: 'shell', command: ['branch', 'merge'] },
|
|
116
|
+
{ relativePath: 'scripts/codex-agent.sh', kind: 'shell', command: ['internal', 'run-shell', 'codexAgent'] },
|
|
117
|
+
{ relativePath: 'scripts/review-bot-watch.sh', kind: 'shell', command: ['internal', 'run-shell', 'reviewBot'] },
|
|
118
|
+
{ relativePath: 'scripts/agent-worktree-prune.sh', kind: 'shell', command: ['worktree', 'prune'] },
|
|
119
|
+
{ relativePath: 'scripts/agent-file-locks.py', kind: 'python', command: ['locks'] },
|
|
120
|
+
{ relativePath: 'scripts/openspec/init-plan-workspace.sh', kind: 'shell', command: ['internal', 'run-shell', 'planInit'] },
|
|
121
|
+
{ relativePath: 'scripts/openspec/init-change-workspace.sh', kind: 'shell', command: ['internal', 'run-shell', 'changeInit'] },
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const LEGACY_MANAGED_REPO_FILES = [
|
|
114
125
|
'scripts/agent-branch-start.sh',
|
|
115
126
|
'scripts/agent-branch-finish.sh',
|
|
116
127
|
'scripts/agent-branch-merge.sh',
|
|
128
|
+
'scripts/agent-session-state.js',
|
|
129
|
+
'scripts/codex-agent.sh',
|
|
117
130
|
'scripts/guardex-docker-loader.sh',
|
|
131
|
+
'scripts/install-vscode-active-agents-extension.js',
|
|
132
|
+
'scripts/review-bot-watch.sh',
|
|
118
133
|
'scripts/agent-worktree-prune.sh',
|
|
119
134
|
'scripts/agent-file-locks.py',
|
|
120
135
|
'scripts/guardex-env.sh',
|
|
121
136
|
'scripts/install-agent-git-hooks.sh',
|
|
137
|
+
'scripts/openspec/init-plan-workspace.sh',
|
|
138
|
+
'scripts/openspec/init-change-workspace.sh',
|
|
122
139
|
'.githooks/pre-commit',
|
|
140
|
+
'.githooks/pre-push',
|
|
123
141
|
'.githooks/post-merge',
|
|
142
|
+
'.githooks/post-checkout',
|
|
143
|
+
'.codex/skills/gitguardex/SKILL.md',
|
|
144
|
+
'.codex/skills/guardex-merge-skills-to-dev/SKILL.md',
|
|
145
|
+
'.claude/commands/gitguardex.md',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const REQUIRED_WORKFLOW_FILES = [
|
|
149
|
+
...TEMPLATE_FILES.map((entry) => toDestinationPath(entry)),
|
|
150
|
+
...SCRIPT_SHIMS.map((entry) => entry.relativePath),
|
|
151
|
+
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
124
152
|
'.omx/state/agent-file-locks.json',
|
|
125
153
|
];
|
|
126
154
|
|
|
127
|
-
const
|
|
155
|
+
const LEGACY_MANAGED_PACKAGE_SCRIPTS = {
|
|
128
156
|
'agent:codex': 'bash ./scripts/codex-agent.sh',
|
|
129
157
|
'agent:branch:start': 'bash ./scripts/agent-branch-start.sh',
|
|
130
158
|
'agent:branch:finish': 'bash ./scripts/agent-branch-finish.sh',
|
|
@@ -149,30 +177,44 @@ const REQUIRED_PACKAGE_SCRIPTS = {
|
|
|
149
177
|
'agent:finish': 'gx finish --all',
|
|
150
178
|
};
|
|
151
179
|
|
|
180
|
+
const PACKAGE_SCRIPT_ASSETS = {
|
|
181
|
+
branchStart: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-start.sh'),
|
|
182
|
+
branchFinish: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-finish.sh'),
|
|
183
|
+
branchMerge: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-merge.sh'),
|
|
184
|
+
codexAgent: path.join(TEMPLATE_ROOT, 'scripts', 'codex-agent.sh'),
|
|
185
|
+
reviewBot: path.join(TEMPLATE_ROOT, 'scripts', 'review-bot-watch.sh'),
|
|
186
|
+
worktreePrune: path.join(TEMPLATE_ROOT, 'scripts', 'agent-worktree-prune.sh'),
|
|
187
|
+
lockTool: path.join(TEMPLATE_ROOT, 'scripts', 'agent-file-locks.py'),
|
|
188
|
+
planInit: path.join(TEMPLATE_ROOT, 'scripts', 'openspec', 'init-plan-workspace.sh'),
|
|
189
|
+
changeInit: path.join(TEMPLATE_ROOT, 'scripts', 'openspec', 'init-change-workspace.sh'),
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const USER_LEVEL_SKILL_ASSETS = [
|
|
193
|
+
{
|
|
194
|
+
source: path.join(TEMPLATE_ROOT, 'codex', 'skills', 'gitguardex', 'SKILL.md'),
|
|
195
|
+
destination: path.join('.codex', 'skills', 'gitguardex', 'SKILL.md'),
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
source: path.join(TEMPLATE_ROOT, 'codex', 'skills', 'guardex-merge-skills-to-dev', 'SKILL.md'),
|
|
199
|
+
destination: path.join('.codex', 'skills', 'guardex-merge-skills-to-dev', 'SKILL.md'),
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
source: path.join(TEMPLATE_ROOT, 'claude', 'commands', 'gitguardex.md'),
|
|
203
|
+
destination: path.join('.claude', 'commands', 'gitguardex.md'),
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
152
207
|
const EXECUTABLE_RELATIVE_PATHS = new Set([
|
|
153
|
-
'scripts/agent-
|
|
154
|
-
'scripts/agent-branch-finish.sh',
|
|
155
|
-
'scripts/agent-branch-merge.sh',
|
|
156
|
-
'scripts/codex-agent.sh',
|
|
208
|
+
'scripts/agent-session-state.js',
|
|
157
209
|
'scripts/guardex-docker-loader.sh',
|
|
158
|
-
'scripts/
|
|
159
|
-
|
|
160
|
-
'
|
|
161
|
-
'scripts/install-agent-git-hooks.sh',
|
|
162
|
-
'scripts/openspec/init-plan-workspace.sh',
|
|
163
|
-
'scripts/openspec/init-change-workspace.sh',
|
|
164
|
-
'.githooks/pre-commit',
|
|
165
|
-
'.githooks/pre-push',
|
|
166
|
-
'.githooks/post-merge',
|
|
167
|
-
'.githooks/post-checkout',
|
|
210
|
+
'scripts/install-vscode-active-agents-extension.js',
|
|
211
|
+
...SCRIPT_SHIMS.map((entry) => entry.relativePath),
|
|
212
|
+
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
168
213
|
]);
|
|
169
214
|
|
|
170
215
|
const CRITICAL_GUARDRAIL_PATHS = new Set([
|
|
171
216
|
'AGENTS.md',
|
|
172
|
-
'.githooks
|
|
173
|
-
'.githooks/pre-push',
|
|
174
|
-
'.githooks/post-merge',
|
|
175
|
-
'.githooks/post-checkout',
|
|
217
|
+
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
176
218
|
'scripts/agent-branch-start.sh',
|
|
177
219
|
'scripts/agent-branch-finish.sh',
|
|
178
220
|
'scripts/agent-branch-merge.sh',
|
|
@@ -202,9 +244,6 @@ const MANAGED_GITIGNORE_PATHS = [
|
|
|
202
244
|
'scripts/agent-file-locks.py',
|
|
203
245
|
'.githooks',
|
|
204
246
|
'oh-my-codex/',
|
|
205
|
-
'.codex/skills/gitguardex/SKILL.md',
|
|
206
|
-
'.codex/skills/guardex-merge-skills-to-dev/SKILL.md',
|
|
207
|
-
'.claude/commands/gitguardex.md',
|
|
208
247
|
LOCK_FILE_RELATIVE,
|
|
209
248
|
];
|
|
210
249
|
const REPO_SCAFFOLD_DIRECTORIES = ['bin'];
|
|
@@ -237,6 +276,12 @@ const SUGGESTIBLE_COMMANDS = [
|
|
|
237
276
|
'status',
|
|
238
277
|
'setup',
|
|
239
278
|
'doctor',
|
|
279
|
+
'branch',
|
|
280
|
+
'locks',
|
|
281
|
+
'worktree',
|
|
282
|
+
'hook',
|
|
283
|
+
'migrate',
|
|
284
|
+
'install-agent-skills',
|
|
240
285
|
'agents',
|
|
241
286
|
'merge',
|
|
242
287
|
'finish',
|
|
@@ -262,6 +307,12 @@ const CLI_COMMAND_DESCRIPTIONS = [
|
|
|
262
307
|
['status', 'Show GitGuardex CLI + service health without modifying files'],
|
|
263
308
|
['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target)'],
|
|
264
309
|
['doctor', 'Repair drift + verify (auto-sandboxes on protected main)'],
|
|
310
|
+
['branch', 'CLI-owned branch workflow surface (start/finish/merge)'],
|
|
311
|
+
['locks', 'CLI-owned file lock surface (claim/allow-delete/release/status/validate)'],
|
|
312
|
+
['worktree', 'CLI-owned worktree cleanup surface (prune)'],
|
|
313
|
+
['hook', 'Hook dispatch/install surface used by managed shims'],
|
|
314
|
+
['migrate', 'Convert legacy repo-local installs to the new shim-based CLI-owned surface'],
|
|
315
|
+
['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
|
|
265
316
|
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
|
|
266
317
|
['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
|
|
267
318
|
['sync', 'Sync agent branches with origin/<base>'],
|
|
@@ -272,7 +323,7 @@ const CLI_COMMAND_DESCRIPTIONS = [
|
|
|
272
323
|
['prompt', 'Print AI setup checklist (--exec, --snippet)'],
|
|
273
324
|
['report', 'Security/safety reports (e.g. OpenSSF scorecard)'],
|
|
274
325
|
['help', 'Show this help output'],
|
|
275
|
-
['version', 'Print
|
|
326
|
+
['version', 'Print GitGuardex version'],
|
|
276
327
|
];
|
|
277
328
|
const DEPRECATED_COMMAND_ALIASES = new Map([
|
|
278
329
|
['init', { target: 'setup', hint: 'gx setup' }],
|
|
@@ -306,11 +357,11 @@ function defaultAgentWorktreeRelativeDir(env = process.env) {
|
|
|
306
357
|
|
|
307
358
|
const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in this repo.
|
|
308
359
|
|
|
309
|
-
1) Install:
|
|
360
|
+
1) Install: ${GLOBAL_INSTALL_COMMAND} && gh --version
|
|
310
361
|
2) Bootstrap: gx setup
|
|
311
362
|
3) Repair: gx doctor
|
|
312
|
-
4) Task loop:
|
|
313
|
-
|
|
363
|
+
4) Task loop: gx branch start "<task>" "<agent>"
|
|
364
|
+
then gx locks claim --branch "<agent-branch>" <file...> -> gx branch finish
|
|
314
365
|
5) Integrate: gx merge --branch <agent-a> --branch <agent-b>
|
|
315
366
|
6) Finish: gx finish --all
|
|
316
367
|
7) Cleanup: gx cleanup
|
|
@@ -321,12 +372,12 @@ const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in thi
|
|
|
321
372
|
12) Fork sync: install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml
|
|
322
373
|
`;
|
|
323
374
|
|
|
324
|
-
const AI_SETUP_COMMANDS =
|
|
375
|
+
const AI_SETUP_COMMANDS = `${GLOBAL_INSTALL_COMMAND}
|
|
325
376
|
gh --version
|
|
326
377
|
gx setup
|
|
327
378
|
gx doctor
|
|
328
|
-
|
|
329
|
-
|
|
379
|
+
gx branch start "<task>" "<agent>"
|
|
380
|
+
gx locks claim --branch "<agent-branch>" <file...>
|
|
330
381
|
gx merge --branch "<agent-a>" --branch "<agent-b>"
|
|
331
382
|
gx finish --all
|
|
332
383
|
gx cleanup
|
|
@@ -357,7 +408,17 @@ function runtimeVersion() {
|
|
|
357
408
|
}
|
|
358
409
|
|
|
359
410
|
function supportsAnsiColors() {
|
|
360
|
-
|
|
411
|
+
const forced = String(process.env.FORCE_COLOR || '').trim().toLowerCase();
|
|
412
|
+
if (['0', 'false', 'no', 'off'].includes(forced)) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
if (forced.length > 0) {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
if (process.env.NO_COLOR) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
return Boolean(process.stdout.isTTY) && process.env.TERM !== 'dumb';
|
|
361
422
|
}
|
|
362
423
|
|
|
363
424
|
function colorize(text, colorCode) {
|
|
@@ -367,6 +428,56 @@ function colorize(text, colorCode) {
|
|
|
367
428
|
return `\u001B[${colorCode}m${text}\u001B[0m`;
|
|
368
429
|
}
|
|
369
430
|
|
|
431
|
+
function doctorOutputColorCode(status) {
|
|
432
|
+
const normalized = String(status || '').trim().toLowerCase();
|
|
433
|
+
if (['active', 'done', 'ok', 'safe', 'success'].includes(normalized)) {
|
|
434
|
+
return '32';
|
|
435
|
+
}
|
|
436
|
+
if (normalized === 'disabled') {
|
|
437
|
+
return '36';
|
|
438
|
+
}
|
|
439
|
+
if (['degraded', 'pending', 'skip', 'warn', 'warning'].includes(normalized)) {
|
|
440
|
+
return '33';
|
|
441
|
+
}
|
|
442
|
+
if (['error', 'fail', 'inactive', 'unsafe'].includes(normalized)) {
|
|
443
|
+
return '31';
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function colorizeDoctorOutput(text, status) {
|
|
449
|
+
const colorCode = doctorOutputColorCode(status);
|
|
450
|
+
return colorCode ? colorize(text, colorCode) : text;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function detectAutoFinishDetailStatus(detail) {
|
|
454
|
+
const trimmed = String(detail || '').trim();
|
|
455
|
+
const match = trimmed.match(/^\[(\w+)\]/);
|
|
456
|
+
if (match) {
|
|
457
|
+
return match[1].toLowerCase();
|
|
458
|
+
}
|
|
459
|
+
if (/^Skipped\b/i.test(trimmed) || /^No local agent branches found\b/i.test(trimmed)) {
|
|
460
|
+
return 'skip';
|
|
461
|
+
}
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function detectAutoFinishSummaryStatus(summary) {
|
|
466
|
+
if (!summary || summary.enabled === false) {
|
|
467
|
+
return detectAutoFinishDetailStatus(summary?.details?.[0]);
|
|
468
|
+
}
|
|
469
|
+
if ((summary.failed || 0) > 0) {
|
|
470
|
+
return 'fail';
|
|
471
|
+
}
|
|
472
|
+
if ((summary.completed || 0) > 0) {
|
|
473
|
+
return 'done';
|
|
474
|
+
}
|
|
475
|
+
if ((summary.skipped || 0) > 0) {
|
|
476
|
+
return 'skip';
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
|
|
370
481
|
function statusDot(status) {
|
|
371
482
|
if (status === 'active') {
|
|
372
483
|
return colorize('●', '32'); // green
|
|
@@ -512,10 +623,74 @@ function run(cmd, args, options = {}) {
|
|
|
512
623
|
encoding: 'utf8',
|
|
513
624
|
stdio: options.stdio || 'pipe',
|
|
514
625
|
cwd: options.cwd,
|
|
626
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
515
627
|
timeout: options.timeout,
|
|
516
628
|
});
|
|
517
629
|
}
|
|
518
630
|
|
|
631
|
+
function extractTargetedArgs(rawArgs, defaultTarget = process.cwd()) {
|
|
632
|
+
const passthrough = [];
|
|
633
|
+
let target = defaultTarget;
|
|
634
|
+
|
|
635
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
636
|
+
const arg = rawArgs[index];
|
|
637
|
+
if (arg === '--target' || arg === '-t') {
|
|
638
|
+
target = requireValue(rawArgs, index, '--target');
|
|
639
|
+
index += 1;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
passthrough.push(arg);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return { target, passthrough };
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function packageAssetEnv(extraEnv = {}) {
|
|
649
|
+
return {
|
|
650
|
+
GUARDEX_CLI_ENTRY: __filename,
|
|
651
|
+
GUARDEX_NODE_BIN: process.execPath,
|
|
652
|
+
...extraEnv,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function packageAssetPath(assetKey) {
|
|
657
|
+
const assetPath = PACKAGE_SCRIPT_ASSETS[assetKey];
|
|
658
|
+
if (!assetPath) {
|
|
659
|
+
throw new Error(`Unknown package asset: ${assetKey}`);
|
|
660
|
+
}
|
|
661
|
+
if (!fs.existsSync(assetPath)) {
|
|
662
|
+
throw new Error(`Missing package asset: ${assetPath}`);
|
|
663
|
+
}
|
|
664
|
+
return assetPath;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function runPackageAsset(assetKey, rawArgs, options = {}) {
|
|
668
|
+
const assetPath = packageAssetPath(assetKey);
|
|
669
|
+
let cmd = 'bash';
|
|
670
|
+
if (assetPath.endsWith('.py')) {
|
|
671
|
+
cmd = 'python3';
|
|
672
|
+
} else if (assetPath.endsWith('.js')) {
|
|
673
|
+
cmd = process.execPath;
|
|
674
|
+
}
|
|
675
|
+
return run(cmd, [assetPath, ...rawArgs], {
|
|
676
|
+
cwd: options.cwd || process.cwd(),
|
|
677
|
+
stdio: options.stdio || 'pipe',
|
|
678
|
+
timeout: options.timeout,
|
|
679
|
+
env: packageAssetEnv(options.env),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function invokePackageAsset(assetKey, rawArgs, options = {}) {
|
|
684
|
+
const result = runPackageAsset(assetKey, rawArgs, options);
|
|
685
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
686
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
687
|
+
if (result.status !== 0) {
|
|
688
|
+
throw new Error(`${assetKey} command failed with status ${result.status}`);
|
|
689
|
+
}
|
|
690
|
+
process.exitCode = 0;
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
|
|
519
694
|
function formatElapsedDuration(ms) {
|
|
520
695
|
const durationMs = Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
521
696
|
if (durationMs < 1000) {
|
|
@@ -604,22 +779,29 @@ function printAutoFinishSummary(summary, options = {}) {
|
|
|
604
779
|
|
|
605
780
|
if (enabled) {
|
|
606
781
|
console.log(
|
|
607
|
-
|
|
782
|
+
colorizeDoctorOutput(
|
|
783
|
+
`[${TOOL_NAME}] Auto-finish sweep (base=${baseBranch}): attempted=${summary.attempted}, completed=${summary.completed}, skipped=${summary.skipped}, failed=${summary.failed}`,
|
|
784
|
+
detectAutoFinishSummaryStatus(summary),
|
|
785
|
+
),
|
|
608
786
|
);
|
|
609
787
|
const visibleDetails = verbose ? details : details.slice(0, detailLimit).map(summarizeAutoFinishDetail);
|
|
610
788
|
for (const detail of visibleDetails) {
|
|
611
|
-
console.log(`[${TOOL_NAME}] ${detail}
|
|
789
|
+
console.log(colorizeDoctorOutput(`[${TOOL_NAME}] ${detail}`, detectAutoFinishDetailStatus(detail)));
|
|
612
790
|
}
|
|
613
791
|
if (!verbose && details.length > detailLimit) {
|
|
614
792
|
console.log(
|
|
615
|
-
|
|
793
|
+
colorizeDoctorOutput(
|
|
794
|
+
`[${TOOL_NAME}] … ${details.length - detailLimit} more branch result(s). Re-run with --verbose-auto-finish for full details.`,
|
|
795
|
+
'warn',
|
|
796
|
+
),
|
|
616
797
|
);
|
|
617
798
|
}
|
|
618
799
|
return;
|
|
619
800
|
}
|
|
620
801
|
|
|
621
802
|
if (details.length > 0) {
|
|
622
|
-
|
|
803
|
+
const detail = verbose ? details[0] : summarizeAutoFinishDetail(details[0]);
|
|
804
|
+
console.log(colorizeDoctorOutput(`[${TOOL_NAME}] ${detail}`, detectAutoFinishDetailStatus(detail)));
|
|
623
805
|
}
|
|
624
806
|
}
|
|
625
807
|
|
|
@@ -747,6 +929,9 @@ function toDestinationPath(relativeTemplatePath) {
|
|
|
747
929
|
if (relativeTemplatePath.startsWith('github/')) {
|
|
748
930
|
return `.${relativeTemplatePath}`;
|
|
749
931
|
}
|
|
932
|
+
if (relativeTemplatePath.startsWith('vscode/')) {
|
|
933
|
+
return relativeTemplatePath;
|
|
934
|
+
}
|
|
750
935
|
throw new Error(`Unsupported template path: ${relativeTemplatePath}`);
|
|
751
936
|
}
|
|
752
937
|
|
|
@@ -784,6 +969,111 @@ function isCriticalGuardrailPath(relativePath) {
|
|
|
784
969
|
return CRITICAL_GUARDRAIL_PATHS.has(relativePath);
|
|
785
970
|
}
|
|
786
971
|
|
|
972
|
+
function shellSingleQuote(value) {
|
|
973
|
+
return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function renderShellDispatchShim(commandParts) {
|
|
977
|
+
const rendered = commandParts.map((part) => shellSingleQuote(part)).join(' ');
|
|
978
|
+
return (
|
|
979
|
+
'#!/usr/bin/env bash\n' +
|
|
980
|
+
'set -euo pipefail\n' +
|
|
981
|
+
'\n' +
|
|
982
|
+
'if [[ -n "${GUARDEX_CLI_ENTRY:-}" ]]; then\n' +
|
|
983
|
+
' node_bin="${GUARDEX_NODE_BIN:-node}"\n' +
|
|
984
|
+
` exec "$node_bin" "$GUARDEX_CLI_ENTRY" ${rendered} "$@"\n` +
|
|
985
|
+
'fi\n' +
|
|
986
|
+
'\n' +
|
|
987
|
+
'resolve_guardex_cli() {\n' +
|
|
988
|
+
' if [[ -n "${GUARDEX_CLI_BIN:-}" ]]; then\n' +
|
|
989
|
+
' printf \'%s\' "$GUARDEX_CLI_BIN"\n' +
|
|
990
|
+
' return 0\n' +
|
|
991
|
+
' fi\n' +
|
|
992
|
+
' if command -v gx >/dev/null 2>&1; then\n' +
|
|
993
|
+
' printf \'%s\' "gx"\n' +
|
|
994
|
+
' return 0\n' +
|
|
995
|
+
' fi\n' +
|
|
996
|
+
' if command -v gitguardex >/dev/null 2>&1; then\n' +
|
|
997
|
+
' printf \'%s\' "gitguardex"\n' +
|
|
998
|
+
' return 0\n' +
|
|
999
|
+
' fi\n' +
|
|
1000
|
+
' echo "[gitguardex-shim] Missing gx CLI in PATH." >&2\n' +
|
|
1001
|
+
' exit 1\n' +
|
|
1002
|
+
'}\n' +
|
|
1003
|
+
'\n' +
|
|
1004
|
+
'cli_bin="$(resolve_guardex_cli)"\n' +
|
|
1005
|
+
`exec "$cli_bin" ${rendered} "$@"\n`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function renderPythonDispatchShim(commandParts) {
|
|
1010
|
+
return (
|
|
1011
|
+
'#!/usr/bin/env python3\n' +
|
|
1012
|
+
'import os\n' +
|
|
1013
|
+
'import shutil\n' +
|
|
1014
|
+
'import subprocess\n' +
|
|
1015
|
+
'import sys\n' +
|
|
1016
|
+
'\n' +
|
|
1017
|
+
`COMMAND = ${JSON.stringify(commandParts)}\n` +
|
|
1018
|
+
'\n' +
|
|
1019
|
+
'entry = os.environ.get("GUARDEX_CLI_ENTRY")\n' +
|
|
1020
|
+
'if entry:\n' +
|
|
1021
|
+
' node_bin = os.environ.get("GUARDEX_NODE_BIN") or shutil.which("node") or "node"\n' +
|
|
1022
|
+
' raise SystemExit(subprocess.call([node_bin, entry, *COMMAND, *sys.argv[1:]]))\n' +
|
|
1023
|
+
'cli = os.environ.get("GUARDEX_CLI_BIN") or shutil.which("gx") or shutil.which("gitguardex")\n' +
|
|
1024
|
+
'if not cli:\n' +
|
|
1025
|
+
' sys.stderr.write("[gitguardex-shim] Missing gx CLI in PATH.\\n")\n' +
|
|
1026
|
+
' raise SystemExit(1)\n' +
|
|
1027
|
+
'raise SystemExit(subprocess.call([cli, *COMMAND, *sys.argv[1:]]))\n'
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function renderManagedFile(repoRoot, relativePath, content, options = {}) {
|
|
1032
|
+
const destinationPath = path.join(repoRoot, relativePath);
|
|
1033
|
+
const destinationExists = fs.existsSync(destinationPath);
|
|
1034
|
+
const force = Boolean(options.force);
|
|
1035
|
+
const dryRun = Boolean(options.dryRun);
|
|
1036
|
+
|
|
1037
|
+
if (destinationExists) {
|
|
1038
|
+
const existingContent = fs.readFileSync(destinationPath, 'utf8');
|
|
1039
|
+
if (existingContent === content) {
|
|
1040
|
+
ensureExecutable(destinationPath, relativePath, dryRun);
|
|
1041
|
+
return { status: 'unchanged', file: relativePath };
|
|
1042
|
+
}
|
|
1043
|
+
if (!force && !isCriticalGuardrailPath(relativePath)) {
|
|
1044
|
+
throw new Error(`Refusing to overwrite existing file without --force: ${relativePath}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
ensureParentDir(repoRoot, destinationPath, dryRun);
|
|
1049
|
+
if (!dryRun) {
|
|
1050
|
+
fs.writeFileSync(destinationPath, content, 'utf8');
|
|
1051
|
+
ensureExecutable(destinationPath, relativePath, dryRun);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
if (destinationExists && !force && isCriticalGuardrailPath(relativePath)) {
|
|
1055
|
+
return { status: dryRun ? 'would-repair-critical' : 'repaired-critical', file: relativePath };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return { status: destinationExists ? 'overwritten' : 'created', file: relativePath };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function ensureGeneratedScriptShim(repoRoot, spec, options = {}) {
|
|
1062
|
+
const content = spec.kind === 'python'
|
|
1063
|
+
? renderPythonDispatchShim(spec.command)
|
|
1064
|
+
: renderShellDispatchShim(spec.command);
|
|
1065
|
+
return renderManagedFile(repoRoot, spec.relativePath, content, options);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function ensureHookShim(repoRoot, hookName, options = {}) {
|
|
1069
|
+
return renderManagedFile(
|
|
1070
|
+
repoRoot,
|
|
1071
|
+
path.posix.join('.githooks', hookName),
|
|
1072
|
+
renderShellDispatchShim(['hook', 'run', hookName]),
|
|
1073
|
+
options,
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
787
1077
|
function copyTemplateFile(repoRoot, relativeTemplatePath, force, dryRun) {
|
|
788
1078
|
const sourcePath = path.join(TEMPLATE_ROOT, relativeTemplatePath);
|
|
789
1079
|
const destinationRelativePath = toDestinationPath(relativeTemplatePath);
|
|
@@ -961,8 +1251,7 @@ function writeLockState(repoRoot, payload, dryRun) {
|
|
|
961
1251
|
fs.writeFileSync(lockPath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
962
1252
|
}
|
|
963
1253
|
|
|
964
|
-
function
|
|
965
|
-
const force = Boolean(options.force);
|
|
1254
|
+
function removeLegacyPackageScripts(repoRoot, dryRun) {
|
|
966
1255
|
const packagePath = path.join(repoRoot, 'package.json');
|
|
967
1256
|
if (!fs.existsSync(packagePath)) {
|
|
968
1257
|
return { status: 'skipped', file: 'package.json', note: 'package.json not found' };
|
|
@@ -978,29 +1267,87 @@ function ensurePackageScripts(repoRoot, dryRun, options = {}) {
|
|
|
978
1267
|
const existingScripts = pkg.scripts && typeof pkg.scripts === 'object'
|
|
979
1268
|
? pkg.scripts
|
|
980
1269
|
: {};
|
|
981
|
-
const hasExistingAgentScripts = Object.keys(existingScripts).some((key) => key.startsWith('agent:'));
|
|
982
|
-
if (hasExistingAgentScripts && !force) {
|
|
983
|
-
return { status: 'unchanged', file: 'package.json', note: 'preserved existing agent:* scripts' };
|
|
984
|
-
}
|
|
985
|
-
|
|
986
1270
|
pkg.scripts = existingScripts;
|
|
987
1271
|
let changed = false;
|
|
988
|
-
for (const [key, value] of Object.entries(
|
|
989
|
-
if (
|
|
990
|
-
|
|
1272
|
+
for (const [key, value] of Object.entries(LEGACY_MANAGED_PACKAGE_SCRIPTS)) {
|
|
1273
|
+
if (existingScripts[key] === value) {
|
|
1274
|
+
delete existingScripts[key];
|
|
991
1275
|
changed = true;
|
|
992
1276
|
}
|
|
993
1277
|
}
|
|
994
1278
|
|
|
995
1279
|
if (!changed) {
|
|
996
|
-
return { status: 'unchanged', file: 'package.json' };
|
|
1280
|
+
return { status: 'unchanged', file: 'package.json', note: 'no Guardex-managed agent:* scripts found' };
|
|
997
1281
|
}
|
|
998
1282
|
|
|
999
1283
|
if (!dryRun) {
|
|
1000
1284
|
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
1001
1285
|
}
|
|
1002
1286
|
|
|
1003
|
-
return { status: 'updated', file: 'package.json' };
|
|
1287
|
+
return { status: dryRun ? 'would-update' : 'updated', file: 'package.json', note: 'removed Guardex-managed agent:* scripts' };
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function installUserLevelAsset(asset, options = {}) {
|
|
1291
|
+
const dryRun = Boolean(options.dryRun);
|
|
1292
|
+
const force = Boolean(options.force);
|
|
1293
|
+
const destinationPath = path.join(GUARDEX_HOME_DIR, asset.destination);
|
|
1294
|
+
const sourceContent = fs.readFileSync(asset.source, 'utf8');
|
|
1295
|
+
const destinationExists = fs.existsSync(destinationPath);
|
|
1296
|
+
|
|
1297
|
+
if (destinationExists) {
|
|
1298
|
+
const existingContent = fs.readFileSync(destinationPath, 'utf8');
|
|
1299
|
+
if (existingContent === sourceContent) {
|
|
1300
|
+
return { status: 'unchanged', file: asset.destination };
|
|
1301
|
+
}
|
|
1302
|
+
if (!force) {
|
|
1303
|
+
return { status: 'skipped-conflict', file: asset.destination };
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (!dryRun) {
|
|
1308
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
1309
|
+
fs.writeFileSync(destinationPath, sourceContent, 'utf8');
|
|
1310
|
+
}
|
|
1311
|
+
return { status: destinationExists ? (dryRun ? 'would-update' : 'updated') : 'created', file: asset.destination };
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function removeLegacyManagedRepoFile(repoRoot, relativePath, options = {}) {
|
|
1315
|
+
const dryRun = Boolean(options.dryRun);
|
|
1316
|
+
const force = Boolean(options.force);
|
|
1317
|
+
const absolutePath = path.join(repoRoot, relativePath);
|
|
1318
|
+
if (!fs.existsSync(absolutePath)) {
|
|
1319
|
+
return { status: 'unchanged', file: relativePath, note: 'not present' };
|
|
1320
|
+
}
|
|
1321
|
+
if (!fs.statSync(absolutePath).isFile()) {
|
|
1322
|
+
return { status: 'skipped-conflict', file: relativePath, note: 'not a regular file' };
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const skillAsset = USER_LEVEL_SKILL_ASSETS.find((asset) => asset.destination === relativePath);
|
|
1326
|
+
if (skillAsset) {
|
|
1327
|
+
const userLevelPath = path.join(GUARDEX_HOME_DIR, skillAsset.destination);
|
|
1328
|
+
if (!fs.existsSync(userLevelPath)) {
|
|
1329
|
+
return { status: 'skipped', file: relativePath, note: 'user-level replacement not installed' };
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const templateRelative = skillAsset
|
|
1334
|
+
? skillAsset.source.slice(TEMPLATE_ROOT.length + 1)
|
|
1335
|
+
: relativePath.replace(/^\./, '');
|
|
1336
|
+
const sourcePath = path.join(TEMPLATE_ROOT, templateRelative);
|
|
1337
|
+
if (!fs.existsSync(sourcePath)) {
|
|
1338
|
+
return { status: 'skipped', file: relativePath, note: 'template source missing' };
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf8');
|
|
1342
|
+
const existingContent = fs.readFileSync(absolutePath, 'utf8');
|
|
1343
|
+
if (existingContent !== sourceContent && !force) {
|
|
1344
|
+
return { status: 'skipped-conflict', file: relativePath, note: 'local edits differ from managed template' };
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (!dryRun) {
|
|
1348
|
+
fs.rmSync(absolutePath, { force: true });
|
|
1349
|
+
}
|
|
1350
|
+
return { status: dryRun ? 'would-remove' : 'removed', file: relativePath };
|
|
1004
1351
|
}
|
|
1005
1352
|
|
|
1006
1353
|
function ensureAgentsSnippet(repoRoot, dryRun, options = {}) {
|
|
@@ -1366,7 +1713,7 @@ function assertProtectedMainWriteAllowed(options, commandName) {
|
|
|
1366
1713
|
throw new Error(
|
|
1367
1714
|
`${commandName} blocked on protected branch '${blocked.branch}' in an initialized repo.\n` +
|
|
1368
1715
|
`Keep local '${blocked.branch}' pull-only: start an agent branch/worktree first:\n` +
|
|
1369
|
-
`
|
|
1716
|
+
` gx branch start "<task>" "codex"\n` +
|
|
1370
1717
|
`Override once only when intentional: --allow-protected-base-write`,
|
|
1371
1718
|
);
|
|
1372
1719
|
}
|
|
@@ -1592,8 +1939,7 @@ function startProtectedBaseSandbox(blocked, { taskName, sandboxSuffix }) {
|
|
|
1592
1939
|
return startProtectedBaseSandboxFallback(blocked, sandboxSuffix);
|
|
1593
1940
|
}
|
|
1594
1941
|
|
|
1595
|
-
const startResult =
|
|
1596
|
-
startScript,
|
|
1942
|
+
const startResult = runPackageAsset('branchStart', [
|
|
1597
1943
|
'--task',
|
|
1598
1944
|
taskName,
|
|
1599
1945
|
'--agent',
|
|
@@ -1742,8 +2088,7 @@ function collectWorktreeDirtyPaths(worktreePath) {
|
|
|
1742
2088
|
}
|
|
1743
2089
|
|
|
1744
2090
|
function collectDoctorForceAddPaths(worktreePath) {
|
|
1745
|
-
return
|
|
1746
|
-
.map((entry) => toDestinationPath(entry))
|
|
2091
|
+
return REQUIRED_WORKFLOW_FILES
|
|
1747
2092
|
.filter((relativePath) => relativePath.startsWith('scripts/') || relativePath.startsWith('.githooks/'))
|
|
1748
2093
|
.filter((relativePath) => fs.existsSync(path.join(worktreePath, relativePath)));
|
|
1749
2094
|
}
|
|
@@ -1795,13 +2140,13 @@ function claimDoctorChangedLocks(metadata) {
|
|
|
1795
2140
|
]));
|
|
1796
2141
|
const deletedPaths = collectDoctorDeletedPaths(metadata.worktreePath);
|
|
1797
2142
|
if (changedPaths.length > 0) {
|
|
1798
|
-
|
|
2143
|
+
runPackageAsset('lockTool', ['claim', '--branch', metadata.branch, ...changedPaths], {
|
|
1799
2144
|
cwd: metadata.worktreePath,
|
|
1800
2145
|
timeout: 30_000,
|
|
1801
2146
|
});
|
|
1802
2147
|
}
|
|
1803
2148
|
if (deletedPaths.length > 0) {
|
|
1804
|
-
|
|
2149
|
+
runPackageAsset('lockTool', ['allow-delete', '--branch', metadata.branch, ...deletedPaths], {
|
|
1805
2150
|
cwd: metadata.worktreePath,
|
|
1806
2151
|
timeout: 30_000,
|
|
1807
2152
|
});
|
|
@@ -1947,7 +2292,7 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
|
|
|
1947
2292
|
|
|
1948
2293
|
const finishResult = run(
|
|
1949
2294
|
'bash',
|
|
1950
|
-
[finishScript, '--branch', metadata.branch, '--base', blocked.branch, '--via-pr', waitForMergeArg],
|
|
2295
|
+
[finishScript, '--branch', metadata.branch, '--base', blocked.branch, '--via-pr', waitForMergeArg, '--cleanup'],
|
|
1951
2296
|
{ cwd: metadata.worktreePath, timeout: finishTimeoutMs },
|
|
1952
2297
|
);
|
|
1953
2298
|
if (isSpawnFailure(finishResult)) {
|
|
@@ -2018,7 +2363,7 @@ function mergeDoctorSandboxRepairsBackToProtectedBase(options, blocked, metadata
|
|
|
2018
2363
|
...(autoCommitResult.stagedFiles || []),
|
|
2019
2364
|
...OMX_SCAFFOLD_DIRECTORIES,
|
|
2020
2365
|
...Array.from(OMX_SCAFFOLD_FILES.keys()),
|
|
2021
|
-
...
|
|
2366
|
+
...REQUIRED_WORKFLOW_FILES,
|
|
2022
2367
|
'bin',
|
|
2023
2368
|
'package.json',
|
|
2024
2369
|
'.gitignore',
|
|
@@ -2156,9 +2501,7 @@ function mergeDoctorSandboxRepairsBackToProtectedBase(options, blocked, metadata
|
|
|
2156
2501
|
}
|
|
2157
2502
|
|
|
2158
2503
|
function syncDoctorLocalSupportFiles(repoRoot, dryRun) {
|
|
2159
|
-
return
|
|
2160
|
-
.filter((entry) => entry.startsWith('codex/') || entry.startsWith('claude/'))
|
|
2161
|
-
.map((entry) => ensureTemplateFilePresent(repoRoot, entry, dryRun));
|
|
2504
|
+
return [];
|
|
2162
2505
|
}
|
|
2163
2506
|
|
|
2164
2507
|
function runDoctorInSandbox(options, blocked) {
|
|
@@ -2438,7 +2781,7 @@ function runDoctorInSandbox(options, blocked) {
|
|
|
2438
2781
|
if (finishResult.stderr) process.stderr.write(finishResult.stderr);
|
|
2439
2782
|
} else if (finishResult.status === 'failed') {
|
|
2440
2783
|
console.log(`[${TOOL_NAME}] Auto-finish flow failed for sandbox branch '${metadata.branch}'.`);
|
|
2441
|
-
console.log(`[
|
|
2784
|
+
console.log(`[${TOOL_NAME}] Auto-finish flow failed for sandbox branch '${metadata.branch}'.`);
|
|
2442
2785
|
if (finishResult.stdout) process.stdout.write(finishResult.stdout);
|
|
2443
2786
|
if (finishResult.stderr) process.stderr.write(finishResult.stderr);
|
|
2444
2787
|
} else {
|
|
@@ -3116,7 +3459,6 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
3116
3459
|
|
|
3117
3460
|
summary.attempted += 1;
|
|
3118
3461
|
const finishArgs = [
|
|
3119
|
-
finishScript,
|
|
3120
3462
|
'--branch',
|
|
3121
3463
|
branch,
|
|
3122
3464
|
'--base',
|
|
@@ -3125,7 +3467,7 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
3125
3467
|
waitForMerge ? '--wait-for-merge' : '--no-wait-for-merge',
|
|
3126
3468
|
'--cleanup',
|
|
3127
3469
|
];
|
|
3128
|
-
const finishResult =
|
|
3470
|
+
const finishResult = runPackageAsset('branchFinish', finishArgs, { cwd: repoRoot });
|
|
3129
3471
|
const combinedOutput = [finishResult.stdout || '', finishResult.stderr || ''].join('\n').trim();
|
|
3130
3472
|
|
|
3131
3473
|
if (finishResult.status === 0) {
|
|
@@ -3278,9 +3620,9 @@ function printSetupRepoHints(repoRoot, baseBranch, repoLabel = '') {
|
|
|
3278
3620
|
console.log(`[${TOOL_NAME}] Bootstrap commit${label}: git add . && git commit -m "bootstrap gitguardex"`);
|
|
3279
3621
|
console.log(
|
|
3280
3622
|
`[${TOOL_NAME}] First agent flow${label}: ` +
|
|
3281
|
-
`
|
|
3282
|
-
`
|
|
3283
|
-
`
|
|
3623
|
+
`gx branch start "<task>" "codex" -> ` +
|
|
3624
|
+
`gx locks claim --branch "$(git branch --show-current)" <file...> -> ` +
|
|
3625
|
+
`gx branch finish --branch "$(git branch --show-current)" --base ${baseBranch} --via-pr --wait-for-merge`,
|
|
3284
3626
|
);
|
|
3285
3627
|
}
|
|
3286
3628
|
if (!hasOrigin) {
|
|
@@ -3628,19 +3970,20 @@ function parseMergeArgs(rawArgs) {
|
|
|
3628
3970
|
return options;
|
|
3629
3971
|
}
|
|
3630
3972
|
|
|
3631
|
-
function parseFinishArgs(rawArgs) {
|
|
3973
|
+
function parseFinishArgs(rawArgs, defaults = {}) {
|
|
3632
3974
|
const options = {
|
|
3633
3975
|
target: process.cwd(),
|
|
3634
3976
|
base: '',
|
|
3635
3977
|
branch: '',
|
|
3636
3978
|
all: false,
|
|
3637
3979
|
dryRun: false,
|
|
3638
|
-
waitForMerge: true,
|
|
3639
|
-
cleanup: true,
|
|
3980
|
+
waitForMerge: defaults.waitForMerge ?? true,
|
|
3981
|
+
cleanup: defaults.cleanup ?? true,
|
|
3640
3982
|
keepRemote: false,
|
|
3641
3983
|
noAutoCommit: false,
|
|
3642
3984
|
failFast: false,
|
|
3643
3985
|
commitMessage: '',
|
|
3986
|
+
mergeMode: defaults.mergeMode || 'pr',
|
|
3644
3987
|
};
|
|
3645
3988
|
|
|
3646
3989
|
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
@@ -3697,6 +4040,26 @@ function parseFinishArgs(rawArgs) {
|
|
|
3697
4040
|
options.waitForMerge = false;
|
|
3698
4041
|
continue;
|
|
3699
4042
|
}
|
|
4043
|
+
if (arg === '--via-pr') {
|
|
4044
|
+
options.mergeMode = 'pr';
|
|
4045
|
+
continue;
|
|
4046
|
+
}
|
|
4047
|
+
if (arg === '--direct-only') {
|
|
4048
|
+
options.mergeMode = 'direct';
|
|
4049
|
+
continue;
|
|
4050
|
+
}
|
|
4051
|
+
if (arg === '--mode') {
|
|
4052
|
+
const next = rawArgs[index + 1];
|
|
4053
|
+
if (!next) {
|
|
4054
|
+
throw new Error('--mode requires a value');
|
|
4055
|
+
}
|
|
4056
|
+
if (!['auto', 'direct', 'pr'].includes(next)) {
|
|
4057
|
+
throw new Error(`Invalid --mode value: ${next} (expected auto|direct|pr)`);
|
|
4058
|
+
}
|
|
4059
|
+
options.mergeMode = next;
|
|
4060
|
+
index += 1;
|
|
4061
|
+
continue;
|
|
4062
|
+
}
|
|
3700
4063
|
if (arg === '--cleanup') {
|
|
3701
4064
|
options.cleanup = true;
|
|
3702
4065
|
continue;
|
|
@@ -3861,7 +4224,7 @@ function claimLocksForAutoCommit(repoRoot, worktreePath, branch) {
|
|
|
3861
4224
|
]);
|
|
3862
4225
|
|
|
3863
4226
|
if (changedFiles.length > 0) {
|
|
3864
|
-
const claim =
|
|
4227
|
+
const claim = runPackageAsset('lockTool', ['claim', '--branch', branch, ...changedFiles], {
|
|
3865
4228
|
cwd: repoRoot,
|
|
3866
4229
|
stdio: 'pipe',
|
|
3867
4230
|
});
|
|
@@ -3895,7 +4258,7 @@ function claimLocksForAutoCommit(repoRoot, worktreePath, branch) {
|
|
|
3895
4258
|
]);
|
|
3896
4259
|
|
|
3897
4260
|
if (deletedFiles.length > 0) {
|
|
3898
|
-
const allowDelete =
|
|
4261
|
+
const allowDelete = runPackageAsset('lockTool', ['allow-delete', '--branch', branch, ...deletedFiles], {
|
|
3899
4262
|
cwd: repoRoot,
|
|
3900
4263
|
stdio: 'pipe',
|
|
3901
4264
|
});
|
|
@@ -4673,6 +5036,16 @@ function askGlobalInstallForMissing(options, missingPackages, missingLocalTools)
|
|
|
4673
5036
|
}
|
|
4674
5037
|
|
|
4675
5038
|
function installGlobalToolchain(options) {
|
|
5039
|
+
const approval = resolveGlobalInstallApproval(options);
|
|
5040
|
+
if (approval.source === 'flag' && !approval.approved) {
|
|
5041
|
+
return {
|
|
5042
|
+
status: 'skipped',
|
|
5043
|
+
reason: approval.source,
|
|
5044
|
+
missingPackages: [],
|
|
5045
|
+
missingLocalTools: [],
|
|
5046
|
+
};
|
|
5047
|
+
}
|
|
5048
|
+
|
|
4676
5049
|
if (options.dryRun) {
|
|
4677
5050
|
return { status: 'dry-run-skip' };
|
|
4678
5051
|
}
|
|
@@ -4701,11 +5074,11 @@ function installGlobalToolchain(options) {
|
|
|
4701
5074
|
|
|
4702
5075
|
const missingPackages = detection.ok ? detection.missing : [...GLOBAL_TOOLCHAIN_PACKAGES];
|
|
4703
5076
|
const missingLocalTools = localCompanionTools.filter((tool) => tool.status !== 'active');
|
|
4704
|
-
const
|
|
4705
|
-
if (!
|
|
5077
|
+
const installApproval = askGlobalInstallForMissing(options, missingPackages, missingLocalTools);
|
|
5078
|
+
if (!installApproval.approved) {
|
|
4706
5079
|
return {
|
|
4707
5080
|
status: 'skipped',
|
|
4708
|
-
reason:
|
|
5081
|
+
reason: installApproval.source,
|
|
4709
5082
|
missingPackages,
|
|
4710
5083
|
missingLocalTools,
|
|
4711
5084
|
};
|
|
@@ -4800,13 +5173,15 @@ function runInstallInternal(options) {
|
|
|
4800
5173
|
for (const templateFile of TEMPLATE_FILES) {
|
|
4801
5174
|
operations.push(copyTemplateFile(repoRoot, templateFile, Boolean(options.force), Boolean(options.dryRun)));
|
|
4802
5175
|
}
|
|
5176
|
+
for (const shim of SCRIPT_SHIMS) {
|
|
5177
|
+
operations.push(ensureGeneratedScriptShim(repoRoot, shim, options));
|
|
5178
|
+
}
|
|
5179
|
+
for (const hookName of HOOK_NAMES) {
|
|
5180
|
+
operations.push(ensureHookShim(repoRoot, hookName, options));
|
|
5181
|
+
}
|
|
4803
5182
|
|
|
4804
5183
|
operations.push(ensureLockRegistry(repoRoot, Boolean(options.dryRun)));
|
|
4805
5184
|
|
|
4806
|
-
if (!options.skipPackageJson) {
|
|
4807
|
-
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
4808
|
-
}
|
|
4809
|
-
|
|
4810
5185
|
if (!options.skipAgents) {
|
|
4811
5186
|
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
4812
5187
|
}
|
|
@@ -4845,6 +5220,12 @@ function runFixInternal(options) {
|
|
|
4845
5220
|
for (const templateFile of TEMPLATE_FILES) {
|
|
4846
5221
|
operations.push(ensureTemplateFilePresent(repoRoot, templateFile, Boolean(options.dryRun)));
|
|
4847
5222
|
}
|
|
5223
|
+
for (const shim of SCRIPT_SHIMS) {
|
|
5224
|
+
operations.push(ensureGeneratedScriptShim(repoRoot, shim, options));
|
|
5225
|
+
}
|
|
5226
|
+
for (const hookName of HOOK_NAMES) {
|
|
5227
|
+
operations.push(ensureHookShim(repoRoot, hookName, options));
|
|
5228
|
+
}
|
|
4848
5229
|
|
|
4849
5230
|
operations.push(ensureLockRegistry(repoRoot, Boolean(options.dryRun)));
|
|
4850
5231
|
|
|
@@ -4874,10 +5255,6 @@ function runFixInternal(options) {
|
|
|
4874
5255
|
}
|
|
4875
5256
|
}
|
|
4876
5257
|
|
|
4877
|
-
if (!options.skipPackageJson) {
|
|
4878
|
-
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
4879
|
-
}
|
|
4880
|
-
|
|
4881
5258
|
if (!options.skipAgents) {
|
|
4882
5259
|
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
4883
5260
|
}
|
|
@@ -4907,8 +5284,7 @@ function runScanInternal(options) {
|
|
|
4907
5284
|
const requiredPaths = [
|
|
4908
5285
|
...OMX_SCAFFOLD_DIRECTORIES,
|
|
4909
5286
|
...Array.from(OMX_SCAFFOLD_FILES.keys()),
|
|
4910
|
-
...
|
|
4911
|
-
LOCK_FILE_RELATIVE,
|
|
5287
|
+
...REQUIRED_WORKFLOW_FILES,
|
|
4912
5288
|
];
|
|
4913
5289
|
|
|
4914
5290
|
for (const relativePath of requiredPaths) {
|
|
@@ -5043,21 +5419,34 @@ function printScanResult(scan, json = false) {
|
|
|
5043
5419
|
|
|
5044
5420
|
if (scan.guardexEnabled === false) {
|
|
5045
5421
|
console.log(
|
|
5046
|
-
|
|
5422
|
+
colorizeDoctorOutput(
|
|
5423
|
+
`[${TOOL_NAME}] Guardex is disabled for this repo (${describeGuardexRepoToggle(scan.guardexToggle)}).`,
|
|
5424
|
+
'disabled',
|
|
5425
|
+
),
|
|
5047
5426
|
);
|
|
5048
5427
|
return;
|
|
5049
5428
|
}
|
|
5050
5429
|
|
|
5051
5430
|
if (scan.findings.length === 0) {
|
|
5052
|
-
console.log(`[${TOOL_NAME}] ✅ No safety issues detected
|
|
5431
|
+
console.log(colorizeDoctorOutput(`[${TOOL_NAME}] ✅ No safety issues detected.`, 'safe'));
|
|
5053
5432
|
return;
|
|
5054
5433
|
}
|
|
5055
5434
|
|
|
5056
5435
|
for (const item of scan.findings) {
|
|
5057
5436
|
const target = item.path ? ` (${item.path})` : '';
|
|
5058
|
-
console.log(
|
|
5437
|
+
console.log(
|
|
5438
|
+
colorizeDoctorOutput(
|
|
5439
|
+
`[${item.level.toUpperCase()}] ${item.code}${target}: ${item.message}`,
|
|
5440
|
+
item.level,
|
|
5441
|
+
),
|
|
5442
|
+
);
|
|
5059
5443
|
}
|
|
5060
|
-
console.log(
|
|
5444
|
+
console.log(
|
|
5445
|
+
colorizeDoctorOutput(
|
|
5446
|
+
`[${TOOL_NAME}] Summary: ${scan.errors} error(s), ${scan.warnings} warning(s).`,
|
|
5447
|
+
scan.errors > 0 ? 'error' : 'warn',
|
|
5448
|
+
),
|
|
5449
|
+
);
|
|
5061
5450
|
}
|
|
5062
5451
|
|
|
5063
5452
|
function setExitCodeFromScan(scan) {
|
|
@@ -5498,10 +5887,13 @@ function doctor(rawArgs) {
|
|
|
5498
5887
|
verbose: singleRepoOptions.verboseAutoFinish,
|
|
5499
5888
|
});
|
|
5500
5889
|
if (safe) {
|
|
5501
|
-
console.log(`[${TOOL_NAME}] ✅ Repo is fully safe
|
|
5890
|
+
console.log(colorizeDoctorOutput(`[${TOOL_NAME}] ✅ Repo is fully safe.`, 'safe'));
|
|
5502
5891
|
} else {
|
|
5503
5892
|
console.log(
|
|
5504
|
-
|
|
5893
|
+
colorizeDoctorOutput(
|
|
5894
|
+
`[${TOOL_NAME}] ⚠️ Repo is not fully safe yet (${scanResult.errors} error(s), ${scanResult.warnings} warning(s)).`,
|
|
5895
|
+
scanResult.errors > 0 ? 'unsafe' : 'warn',
|
|
5896
|
+
),
|
|
5505
5897
|
);
|
|
5506
5898
|
}
|
|
5507
5899
|
setExitCodeFromScan(scanResult);
|
|
@@ -6511,17 +6903,18 @@ function doctorAudit(rawArgs) {
|
|
|
6511
6903
|
|
|
6512
6904
|
const packagePath = path.join(repoRoot, 'package.json');
|
|
6513
6905
|
if (!fs.existsSync(packagePath)) {
|
|
6514
|
-
warn('package.json not found (
|
|
6906
|
+
warn('package.json not found (legacy agent:* script drift cannot be checked)');
|
|
6515
6907
|
} else {
|
|
6516
6908
|
try {
|
|
6517
6909
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
6518
6910
|
const scripts = pkg.scripts || {};
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6911
|
+
const legacyAgentScripts = Object.entries(LEGACY_MANAGED_PACKAGE_SCRIPTS)
|
|
6912
|
+
.filter(([name, expectedValue]) => scripts[name] === expectedValue)
|
|
6913
|
+
.map(([name]) => name);
|
|
6914
|
+
if (legacyAgentScripts.length > 0) {
|
|
6915
|
+
warn(`legacy agent:* package.json scripts remain (${legacyAgentScripts.join(', ')}); run '${SHORT_TOOL_NAME} migrate' to remove them`);
|
|
6916
|
+
} else {
|
|
6917
|
+
ok('package.json does not contain Guardex-managed agent:* helper scripts');
|
|
6525
6918
|
}
|
|
6526
6919
|
} catch (error) {
|
|
6527
6920
|
fail(`package.json is invalid JSON: ${error.message}`);
|
|
@@ -6603,6 +6996,167 @@ function prompt(rawArgs) {
|
|
|
6603
6996
|
return copyPrompt();
|
|
6604
6997
|
}
|
|
6605
6998
|
|
|
6999
|
+
function printStandaloneOperations(title, rootLabel, operations, dryRun = false) {
|
|
7000
|
+
console.log(`[${TOOL_NAME}] ${title}: ${rootLabel}`);
|
|
7001
|
+
for (const operation of operations) {
|
|
7002
|
+
const note = operation.note ? ` (${operation.note})` : '';
|
|
7003
|
+
console.log(` - ${operation.status.padEnd(12)} ${operation.file}${note}`);
|
|
7004
|
+
}
|
|
7005
|
+
if (dryRun) {
|
|
7006
|
+
console.log(`[${TOOL_NAME}] Dry run complete. No files were modified.`);
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
|
|
7010
|
+
function branch(rawArgs) {
|
|
7011
|
+
const [subcommand, ...rest] = rawArgs;
|
|
7012
|
+
if (subcommand === 'start') {
|
|
7013
|
+
const { target, passthrough } = extractTargetedArgs(rest);
|
|
7014
|
+
invokePackageAsset('branchStart', passthrough, { cwd: resolveRepoRoot(target) });
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
if (subcommand === 'finish') {
|
|
7018
|
+
const { target, passthrough } = extractTargetedArgs(rest);
|
|
7019
|
+
invokePackageAsset('branchFinish', passthrough, { cwd: resolveRepoRoot(target) });
|
|
7020
|
+
return;
|
|
7021
|
+
}
|
|
7022
|
+
if (subcommand === 'merge') return merge(rest);
|
|
7023
|
+
throw new Error(
|
|
7024
|
+
`Usage: ${SHORT_TOOL_NAME} branch <start|finish|merge> [options] ` +
|
|
7025
|
+
`(examples: '${SHORT_TOOL_NAME} branch start "<task>" "<agent>"', '${SHORT_TOOL_NAME} branch finish --branch <agent/...>')`,
|
|
7026
|
+
);
|
|
7027
|
+
}
|
|
7028
|
+
|
|
7029
|
+
function locks(rawArgs) {
|
|
7030
|
+
const { target, passthrough } = extractTargetedArgs(rawArgs);
|
|
7031
|
+
const result = runPackageAsset('lockTool', passthrough, { cwd: resolveRepoRoot(target) });
|
|
7032
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
7033
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
7034
|
+
process.exitCode = result.status;
|
|
7035
|
+
}
|
|
7036
|
+
|
|
7037
|
+
function worktree(rawArgs) {
|
|
7038
|
+
const [subcommand, ...rest] = rawArgs;
|
|
7039
|
+
if (subcommand === 'prune') {
|
|
7040
|
+
const { target, passthrough } = extractTargetedArgs(rest);
|
|
7041
|
+
invokePackageAsset('worktreePrune', passthrough, { cwd: resolveRepoRoot(target) });
|
|
7042
|
+
return;
|
|
7043
|
+
}
|
|
7044
|
+
throw new Error(`Usage: ${SHORT_TOOL_NAME} worktree prune [cleanup-options]`);
|
|
7045
|
+
}
|
|
7046
|
+
|
|
7047
|
+
function hook(rawArgs) {
|
|
7048
|
+
const [subcommand, ...rest] = rawArgs;
|
|
7049
|
+
if (subcommand === 'run') {
|
|
7050
|
+
const [hookName, ...hookArgs] = rest;
|
|
7051
|
+
if (!HOOK_NAMES.includes(hookName)) {
|
|
7052
|
+
throw new Error(`Unknown hook name: ${hookName || '(missing)'}`);
|
|
7053
|
+
}
|
|
7054
|
+
const { target, passthrough } = extractTargetedArgs(hookArgs);
|
|
7055
|
+
const hookAssetPath = path.join(TEMPLATE_ROOT, 'githooks', hookName);
|
|
7056
|
+
const result = run('bash', [hookAssetPath, ...passthrough], {
|
|
7057
|
+
cwd: resolveRepoRoot(target),
|
|
7058
|
+
stdio: hookName === 'pre-push' ? 'inherit' : 'pipe',
|
|
7059
|
+
env: packageAssetEnv(),
|
|
7060
|
+
});
|
|
7061
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
7062
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
7063
|
+
process.exitCode = result.status;
|
|
7064
|
+
return;
|
|
7065
|
+
}
|
|
7066
|
+
if (subcommand === 'install') {
|
|
7067
|
+
const { target, passthrough } = extractTargetedArgs(rest);
|
|
7068
|
+
if (passthrough.length > 0) {
|
|
7069
|
+
throw new Error(`Unknown hook install option: ${passthrough[0]}`);
|
|
7070
|
+
}
|
|
7071
|
+
const repoRoot = resolveRepoRoot(target);
|
|
7072
|
+
const hookResult = configureHooks(repoRoot, false);
|
|
7073
|
+
console.log(`[${TOOL_NAME}] Hook install target: ${repoRoot}`);
|
|
7074
|
+
console.log(` - hooksPath ${hookResult.status} ${hookResult.key}=${hookResult.value}`);
|
|
7075
|
+
process.exitCode = 0;
|
|
7076
|
+
return;
|
|
7077
|
+
}
|
|
7078
|
+
throw new Error(`Usage: ${SHORT_TOOL_NAME} hook <run|install> ...`);
|
|
7079
|
+
}
|
|
7080
|
+
|
|
7081
|
+
function internal(rawArgs) {
|
|
7082
|
+
const [subcommand, assetKey, ...rest] = rawArgs;
|
|
7083
|
+
if (subcommand !== 'run-shell') {
|
|
7084
|
+
throw new Error(`Unknown internal command: ${subcommand || '(missing)'}`);
|
|
7085
|
+
}
|
|
7086
|
+
const { target, passthrough } = extractTargetedArgs(rest);
|
|
7087
|
+
const result = runPackageAsset(assetKey, passthrough, { cwd: resolveRepoRoot(target) });
|
|
7088
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
7089
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
7090
|
+
process.exitCode = result.status;
|
|
7091
|
+
}
|
|
7092
|
+
|
|
7093
|
+
function installAgentSkills(rawArgs) {
|
|
7094
|
+
let dryRun = false;
|
|
7095
|
+
let force = false;
|
|
7096
|
+
for (const arg of rawArgs) {
|
|
7097
|
+
if (arg === '--dry-run') {
|
|
7098
|
+
dryRun = true;
|
|
7099
|
+
continue;
|
|
7100
|
+
}
|
|
7101
|
+
if (arg === '--force') {
|
|
7102
|
+
force = true;
|
|
7103
|
+
continue;
|
|
7104
|
+
}
|
|
7105
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7108
|
+
const operations = USER_LEVEL_SKILL_ASSETS.map((asset) => installUserLevelAsset(asset, { dryRun, force }));
|
|
7109
|
+
printStandaloneOperations('User-level Guardex skills', GUARDEX_HOME_DIR, operations, dryRun);
|
|
7110
|
+
process.exitCode = 0;
|
|
7111
|
+
}
|
|
7112
|
+
|
|
7113
|
+
function migrate(rawArgs) {
|
|
7114
|
+
const { target, passthrough } = extractTargetedArgs(rawArgs);
|
|
7115
|
+
let dryRun = false;
|
|
7116
|
+
let force = false;
|
|
7117
|
+
let installSkills = false;
|
|
7118
|
+
for (const arg of passthrough) {
|
|
7119
|
+
if (arg === '--dry-run') {
|
|
7120
|
+
dryRun = true;
|
|
7121
|
+
continue;
|
|
7122
|
+
}
|
|
7123
|
+
if (arg === '--force') {
|
|
7124
|
+
force = true;
|
|
7125
|
+
continue;
|
|
7126
|
+
}
|
|
7127
|
+
if (arg === '--install-agent-skills') {
|
|
7128
|
+
installSkills = true;
|
|
7129
|
+
continue;
|
|
7130
|
+
}
|
|
7131
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
7132
|
+
}
|
|
7133
|
+
|
|
7134
|
+
const repoRoot = resolveRepoRoot(target);
|
|
7135
|
+
const fixPayload = runFixInternal({
|
|
7136
|
+
target: repoRoot,
|
|
7137
|
+
dryRun,
|
|
7138
|
+
force,
|
|
7139
|
+
skipAgents: false,
|
|
7140
|
+
skipPackageJson: true,
|
|
7141
|
+
skipGitignore: false,
|
|
7142
|
+
dropStaleLocks: true,
|
|
7143
|
+
});
|
|
7144
|
+
printOperations('Migrate/fix', fixPayload, dryRun);
|
|
7145
|
+
|
|
7146
|
+
if (installSkills) {
|
|
7147
|
+
const skillOps = USER_LEVEL_SKILL_ASSETS.map((asset) => installUserLevelAsset(asset, { dryRun, force }));
|
|
7148
|
+
printStandaloneOperations('Migrate/install-agent-skills', GUARDEX_HOME_DIR, skillOps, dryRun);
|
|
7149
|
+
}
|
|
7150
|
+
|
|
7151
|
+
const removableLegacyFiles = LEGACY_MANAGED_REPO_FILES.filter(
|
|
7152
|
+
(relativePath) => !REQUIRED_WORKFLOW_FILES.includes(relativePath),
|
|
7153
|
+
);
|
|
7154
|
+
const removalOps = removableLegacyFiles.map((relativePath) => removeLegacyManagedRepoFile(repoRoot, relativePath, { dryRun, force }));
|
|
7155
|
+
removalOps.push(removeLegacyPackageScripts(repoRoot, dryRun));
|
|
7156
|
+
printStandaloneOperations('Migrate/cleanup', repoRoot, removalOps, dryRun);
|
|
7157
|
+
process.exitCode = 0;
|
|
7158
|
+
}
|
|
7159
|
+
|
|
6606
7160
|
function cleanup(rawArgs) {
|
|
6607
7161
|
const options = parseCleanupArgs(rawArgs);
|
|
6608
7162
|
const repoRoot = resolveRepoRoot(options.target);
|
|
@@ -6611,7 +7165,7 @@ function cleanup(rawArgs) {
|
|
|
6611
7165
|
throw new Error(`Missing cleanup script: ${pruneScript}. Run '${SHORT_TOOL_NAME} setup' first.`);
|
|
6612
7166
|
}
|
|
6613
7167
|
|
|
6614
|
-
const args = [
|
|
7168
|
+
const args = [];
|
|
6615
7169
|
if (options.base) {
|
|
6616
7170
|
args.push('--base', options.base);
|
|
6617
7171
|
}
|
|
@@ -6642,7 +7196,7 @@ function cleanup(rawArgs) {
|
|
|
6642
7196
|
}
|
|
6643
7197
|
|
|
6644
7198
|
const runCleanupCycle = () => {
|
|
6645
|
-
const runResult =
|
|
7199
|
+
const runResult = runPackageAsset('worktreePrune', args, { cwd: repoRoot, stdio: 'inherit' });
|
|
6646
7200
|
if (runResult.status !== 0) {
|
|
6647
7201
|
throw new Error('Cleanup command failed');
|
|
6648
7202
|
}
|
|
@@ -6681,7 +7235,7 @@ function merge(rawArgs) {
|
|
|
6681
7235
|
throw new Error(`Missing merge script: ${mergeScript}. Run '${SHORT_TOOL_NAME} setup' first.`);
|
|
6682
7236
|
}
|
|
6683
7237
|
|
|
6684
|
-
const args = [
|
|
7238
|
+
const args = [];
|
|
6685
7239
|
if (options.base) {
|
|
6686
7240
|
args.push('--base', options.base);
|
|
6687
7241
|
}
|
|
@@ -6698,7 +7252,7 @@ function merge(rawArgs) {
|
|
|
6698
7252
|
args.push('--branch', branch);
|
|
6699
7253
|
}
|
|
6700
7254
|
|
|
6701
|
-
const mergeResult =
|
|
7255
|
+
const mergeResult = runPackageAsset('branchMerge', args, { cwd: repoRoot, stdio: 'pipe' });
|
|
6702
7256
|
if (mergeResult.stdout) {
|
|
6703
7257
|
process.stdout.write(mergeResult.stdout);
|
|
6704
7258
|
}
|
|
@@ -6712,8 +7266,8 @@ function merge(rawArgs) {
|
|
|
6712
7266
|
process.exitCode = 0;
|
|
6713
7267
|
}
|
|
6714
7268
|
|
|
6715
|
-
function finish(rawArgs) {
|
|
6716
|
-
const options = parseFinishArgs(rawArgs);
|
|
7269
|
+
function finish(rawArgs, defaults = {}) {
|
|
7270
|
+
const options = parseFinishArgs(rawArgs, defaults);
|
|
6717
7271
|
const repoRoot = resolveRepoRoot(options.target);
|
|
6718
7272
|
const finishScript = path.join(repoRoot, 'scripts', 'agent-branch-finish.sh');
|
|
6719
7273
|
|
|
@@ -6784,26 +7338,31 @@ function finish(rawArgs) {
|
|
|
6784
7338
|
}
|
|
6785
7339
|
|
|
6786
7340
|
const finishArgs = [
|
|
6787
|
-
finishScript,
|
|
6788
7341
|
'--branch',
|
|
6789
7342
|
branch,
|
|
6790
7343
|
'--base',
|
|
6791
7344
|
baseBranch,
|
|
6792
|
-
'--via-pr',
|
|
6793
7345
|
options.waitForMerge ? '--wait-for-merge' : '--no-wait-for-merge',
|
|
6794
7346
|
options.cleanup ? '--cleanup' : '--no-cleanup',
|
|
6795
7347
|
];
|
|
7348
|
+
if (options.mergeMode === 'pr') {
|
|
7349
|
+
finishArgs.push('--via-pr');
|
|
7350
|
+
} else if (options.mergeMode === 'direct') {
|
|
7351
|
+
finishArgs.push('--direct-only');
|
|
7352
|
+
} else {
|
|
7353
|
+
finishArgs.push('--mode', 'auto');
|
|
7354
|
+
}
|
|
6796
7355
|
if (options.keepRemote) {
|
|
6797
7356
|
finishArgs.push('--keep-remote-branch');
|
|
6798
7357
|
}
|
|
6799
7358
|
|
|
6800
7359
|
if (options.dryRun) {
|
|
6801
|
-
console.log(`[${TOOL_NAME}] [dry-run] Would run:
|
|
7360
|
+
console.log(`[${TOOL_NAME}] [dry-run] Would run: gx branch finish ${finishArgs.join(' ')}`);
|
|
6802
7361
|
succeeded += 1;
|
|
6803
7362
|
continue;
|
|
6804
7363
|
}
|
|
6805
7364
|
|
|
6806
|
-
const finishResult =
|
|
7365
|
+
const finishResult = runPackageAsset('branchFinish', finishArgs, { cwd: repoRoot, stdio: 'pipe' });
|
|
6807
7366
|
if (finishResult.stdout) {
|
|
6808
7367
|
process.stdout.write(finishResult.stdout);
|
|
6809
7368
|
}
|
|
@@ -7197,6 +7756,13 @@ function main() {
|
|
|
7197
7756
|
|
|
7198
7757
|
if (command === 'prompt') return prompt(rest);
|
|
7199
7758
|
if (command === 'doctor') return doctor(rest);
|
|
7759
|
+
if (command === 'branch') return branch(rest);
|
|
7760
|
+
if (command === 'locks') return locks(rest);
|
|
7761
|
+
if (command === 'worktree') return worktree(rest);
|
|
7762
|
+
if (command === 'hook') return hook(rest);
|
|
7763
|
+
if (command === 'migrate') return migrate(rest);
|
|
7764
|
+
if (command === 'install-agent-skills') return installAgentSkills(rest);
|
|
7765
|
+
if (command === 'internal') return internal(rest);
|
|
7200
7766
|
if (command === 'agents') return agents(rest);
|
|
7201
7767
|
if (command === 'merge') return merge(rest);
|
|
7202
7768
|
if (command === 'finish') return finish(rest);
|