@imdeadpool/guardex 7.0.41 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -13
- package/package.json +3 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +202 -0
- package/src/agents/launch.js +249 -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 +146 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +344 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +357 -3
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +85 -3613
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- 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/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -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 +304 -43
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +45 -15
- package/src/finish/index.js +186 -7
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +511 -4
- package/src/hooks/index.js +0 -64
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +68 -2
- package/src/pr-review.js +264 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +127 -10
- package/src/speckit/index.js +226 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +45 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +125 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +63 -323
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +44 -20
- 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 +519 -23
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +176 -24
- package/templates/scripts/agent-preflight.sh +115 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -97
- 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
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,
|
|
@@ -111,51 +138,41 @@ function toDestinationPath(relativeTemplatePath) {
|
|
|
111
138
|
if (relativeTemplatePath.startsWith('github/')) {
|
|
112
139
|
return `.${relativeTemplatePath}`;
|
|
113
140
|
}
|
|
114
|
-
if (relativeTemplatePath.startsWith('vscode/')) {
|
|
115
|
-
return relativeTemplatePath;
|
|
116
|
-
}
|
|
117
141
|
throw new Error(`Unsupported template path: ${relativeTemplatePath}`);
|
|
118
142
|
}
|
|
119
143
|
|
|
144
|
+
// scripts/ ↔ templates/scripts/ layout convention (single source of truth):
|
|
145
|
+
//
|
|
146
|
+
// 1. PAIRED files (10): tracked on both sides; scripts/<file> is a symlink
|
|
147
|
+
// to ../templates/scripts/<file> per PR #548. See
|
|
148
|
+
// scripts/check-script-symlinks.sh for the exact list. CI + the
|
|
149
|
+
// .githooks/pre-commit shim both enforce that no symlink is ever
|
|
150
|
+
// replaced with a regular file. Edit only the templates/scripts/ copy;
|
|
151
|
+
// the symlink propagates.
|
|
152
|
+
//
|
|
153
|
+
// 2. SCAFFOLD-ONLY files (the 3 below + workflows):
|
|
154
|
+
// tracked only under templates/; scaffolded into gitignored
|
|
155
|
+
// scripts/<file> (or .githooks/<file>, etc.) by `gx setup`. Consumer
|
|
156
|
+
// repos receive a regular file copy at the destination; gitguardex
|
|
157
|
+
// itself receives the same copy and ignores it via the
|
|
158
|
+
// multiagent-safety .gitignore block. Edit only the templates/ copy.
|
|
159
|
+
//
|
|
160
|
+
// If a file you're about to add fits pattern (1), also add it to
|
|
161
|
+
// scripts/check-script-symlinks.sh's required_symlinks list. If it fits
|
|
162
|
+
// pattern (2), append the destination path to .gitignore's multiagent-
|
|
163
|
+
// safety block (auto-managed by syncManagedGitignoreLines below).
|
|
120
164
|
const TEMPLATE_FILES = [
|
|
121
|
-
'scripts/agent-
|
|
165
|
+
'scripts/agent-preflight.sh',
|
|
122
166
|
'scripts/guardex-docker-loader.sh',
|
|
123
167
|
'scripts/guardex-env.sh',
|
|
124
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
125
168
|
'github/pull.yml.example',
|
|
169
|
+
'github/workflows/ci.yml',
|
|
170
|
+
'github/workflows/ci-full.yml',
|
|
126
171
|
'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',
|
|
172
|
+
'github/workflows/README.md',
|
|
140
173
|
];
|
|
141
174
|
|
|
142
|
-
const PACKAGE_ROOT_SOURCE_OVERRIDES = new Set(
|
|
143
|
-
'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
|
-
]);
|
|
175
|
+
const PACKAGE_ROOT_SOURCE_OVERRIDES = new Set();
|
|
159
176
|
|
|
160
177
|
const LEGACY_WORKFLOW_SHIM_SPECS = [
|
|
161
178
|
{ relativePath: 'scripts/agent-branch-start.sh', kind: 'shell', command: ['branch', 'start'] },
|
|
@@ -178,9 +195,7 @@ const MANAGED_TEMPLATE_SCRIPT_FILES = MANAGED_TEMPLATE_DESTINATIONS.filter((entr
|
|
|
178
195
|
|
|
179
196
|
const LEGACY_MANAGED_REPO_FILES = [
|
|
180
197
|
...LEGACY_WORKFLOW_SHIMS,
|
|
181
|
-
'scripts/agent-session-state.js',
|
|
182
198
|
'scripts/guardex-docker-loader.sh',
|
|
183
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
184
199
|
'scripts/guardex-env.sh',
|
|
185
200
|
'scripts/install-agent-git-hooks.sh',
|
|
186
201
|
'.githooks/pre-commit',
|
|
@@ -229,7 +244,6 @@ const PACKAGE_SCRIPT_ASSETS = {
|
|
|
229
244
|
branchMerge: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-merge.sh'),
|
|
230
245
|
codexAgent: path.join(TEMPLATE_ROOT, 'scripts', 'codex-agent.sh'),
|
|
231
246
|
reviewBot: path.join(TEMPLATE_ROOT, 'scripts', 'review-bot-watch.sh'),
|
|
232
|
-
sessionState: path.join(TEMPLATE_ROOT, 'scripts', 'agent-session-state.js'),
|
|
233
247
|
worktreePrune: path.join(TEMPLATE_ROOT, 'scripts', 'agent-worktree-prune.sh'),
|
|
234
248
|
lockTool: path.join(TEMPLATE_ROOT, 'scripts', 'agent-file-locks.py'),
|
|
235
249
|
planInit: path.join(TEMPLATE_ROOT, 'scripts', 'openspec', 'init-plan-workspace.sh'),
|
|
@@ -266,6 +280,8 @@ const LOCK_FILE_RELATIVE = '.omx/state/agent-file-locks.json';
|
|
|
266
280
|
const AGENTS_BOTS_STATE_RELATIVE = '.omx/state/agents-bots.json';
|
|
267
281
|
const AGENTS_MARKER_START = '<!-- multiagent-safety:START -->';
|
|
268
282
|
const AGENTS_MARKER_END = '<!-- multiagent-safety:END -->';
|
|
283
|
+
const MONOREPO_MARKER_START = '<!-- monorepo-apps:START -->';
|
|
284
|
+
const MONOREPO_MARKER_END = '<!-- monorepo-apps:END -->';
|
|
269
285
|
const GITIGNORE_MARKER_START = '# multiagent-safety:START';
|
|
270
286
|
const GITIGNORE_MARKER_END = '# multiagent-safety:END';
|
|
271
287
|
const CODEX_WORKTREE_RELATIVE_DIR = path.join('.omx', 'agent-worktrees');
|
|
@@ -289,13 +305,12 @@ const MANAGED_REPO_SCAN_IGNORED_FOLDERS = [
|
|
|
289
305
|
const MANAGED_GITIGNORE_PATHS = [
|
|
290
306
|
'.omx/',
|
|
291
307
|
'.omc/',
|
|
308
|
+
'.codex/',
|
|
292
309
|
'!.vscode/',
|
|
293
310
|
'.vscode/*',
|
|
294
311
|
'!.vscode/settings.json',
|
|
295
|
-
'scripts/agent-session-state.js',
|
|
296
312
|
'scripts/guardex-docker-loader.sh',
|
|
297
313
|
'scripts/guardex-env.sh',
|
|
298
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
299
314
|
'.githooks',
|
|
300
315
|
'oh-my-codex/',
|
|
301
316
|
LOCK_FILE_RELATIVE,
|
|
@@ -345,6 +360,7 @@ const SUGGESTIBLE_COMMANDS = [
|
|
|
345
360
|
'hook',
|
|
346
361
|
'migrate',
|
|
347
362
|
'install-agent-skills',
|
|
363
|
+
'cockpit',
|
|
348
364
|
'agents',
|
|
349
365
|
'merge',
|
|
350
366
|
'finish',
|
|
@@ -364,6 +380,9 @@ const SUGGESTIBLE_COMMANDS = [
|
|
|
364
380
|
'copy-commands',
|
|
365
381
|
'print-agents-snippet',
|
|
366
382
|
'release',
|
|
383
|
+
'budget',
|
|
384
|
+
'ci-init',
|
|
385
|
+
'speckit',
|
|
367
386
|
];
|
|
368
387
|
// CLI_COMMAND_GROUPS is the grouped source of truth the `gx --help` /
|
|
369
388
|
// `gx` no-args renderer uses. Each group is ordered roughly by how often a
|
|
@@ -409,7 +428,10 @@ const CLI_COMMAND_GROUPS = [
|
|
|
409
428
|
description: 'Review / cleanup bots, AI setup prompts, and safety reports.',
|
|
410
429
|
commands: [
|
|
411
430
|
['agents', 'Start/stop repo-scoped review + cleanup bots'],
|
|
431
|
+
['pr-review', 'Run local Codex/Claude PR review and post inline GitHub comments or write an artifact'],
|
|
432
|
+
['cockpit', 'Create or attach to a repo tmux cockpit session'],
|
|
412
433
|
['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
|
|
434
|
+
['speckit', 'Install Spec Kit (specify-cli) SDD slash skills (/speckit-specify, /speckit-plan, ...) into the current repo'],
|
|
413
435
|
['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
|
|
414
436
|
['report', 'Security/safety reports (e.g. OpenSSF scorecard, session severity)'],
|
|
415
437
|
['release', 'Create or update the current GitHub release with README-generated notes'],
|
|
@@ -449,6 +471,9 @@ const DOCTOR_AUTO_FINISH_MESSAGE_MAX = 160;
|
|
|
449
471
|
const AI_SETUP_PART_ALIASES = new Map([
|
|
450
472
|
['task', 'task-loop'],
|
|
451
473
|
['loop', 'task-loop'],
|
|
474
|
+
['compact-commands', 'rtk'],
|
|
475
|
+
['command-compression', 'rtk'],
|
|
476
|
+
['token-commands', 'rtk'],
|
|
452
477
|
['reviewbot', 'review-bot'],
|
|
453
478
|
['forksync', 'fork-sync'],
|
|
454
479
|
]);
|
|
@@ -500,6 +525,18 @@ const AI_SETUP_PARTS = [
|
|
|
500
525
|
'gx locks claim --branch "<agent-branch>" <file...>',
|
|
501
526
|
],
|
|
502
527
|
},
|
|
528
|
+
{
|
|
529
|
+
name: 'rtk',
|
|
530
|
+
label: 'RTK command compression',
|
|
531
|
+
promptLines: [
|
|
532
|
+
'Prefer RTK wrappers for noisy shell discovery and verification when `rtk` is available; fall back to raw commands when missing.',
|
|
533
|
+
'Files: `rtk ls .`, `rtk read <file>`, `rtk read <file> -l aggressive`, `rtk smart <file>`, `rtk find "<glob>" .`, `rtk grep "<pattern>" .`, `rtk diff <a> <b>`.',
|
|
534
|
+
'Git/GitHub: `rtk git status`, `rtk git diff`, `rtk git log -n 10`, `rtk gh pr list`, `rtk gh pr view <id>`.',
|
|
535
|
+
'Tests/build: `rtk test <cmd>`, `rtk err <cmd>`, `rtk jest`, `rtk vitest`, `rtk playwright test`, `rtk pytest`, `rtk cargo test`, `rtk tsc`, `rtk lint`.',
|
|
536
|
+
'Use `rtk gain`, `rtk discover`, and `rtk session` to audit savings; use `rtk proxy <command>` only when raw passthrough is required.',
|
|
537
|
+
'Do not wrap machine-readable commands with RTK when code parses stdout (`--porcelain`, `--json`, NUL-delimited output, or exact stdout contracts).',
|
|
538
|
+
],
|
|
539
|
+
},
|
|
503
540
|
{
|
|
504
541
|
name: 'integrate',
|
|
505
542
|
label: 'Integrate',
|
|
@@ -658,11 +695,232 @@ const SCORECARD_RISK_BY_CHECK = {
|
|
|
658
695
|
License: 'Low',
|
|
659
696
|
};
|
|
660
697
|
|
|
698
|
+
// ---------------------------------------------------------------------------
|
|
699
|
+
// Process-scoped memoization for idempotent git/gh probes.
|
|
700
|
+
//
|
|
701
|
+
// Many gx commands (notably `gx doctor`, `gx status`, preflight checks) ask
|
|
702
|
+
// git/gh the same read-only questions multiple times within a single Node
|
|
703
|
+
// process (current branch, remote URL, worktree list, config values, PR
|
|
704
|
+
// state). spawnSync is cheap individually but adds up across 20+ probes.
|
|
705
|
+
//
|
|
706
|
+
// Rules:
|
|
707
|
+
// * Cache only idempotent reads. Strict allowlist below.
|
|
708
|
+
// * Never cache writes, network mutations, or anything passing stdin.
|
|
709
|
+
// * Lifetime = this Node process only (no disk cache, no TTL beyond the
|
|
710
|
+
// process). A fresh `gx ...` invocation always starts cold.
|
|
711
|
+
// * Honors GUARDEX_PROBE_TRACE=1 to print `[probe]` / `[probe-hit]` lines
|
|
712
|
+
// on stderr so duplicate calls are observable.
|
|
713
|
+
// * Honors GUARDEX_PROBE_CACHE=0 to disable the cache entirely
|
|
714
|
+
// (escape hatch).
|
|
715
|
+
// ---------------------------------------------------------------------------
|
|
716
|
+
|
|
717
|
+
const PROBE_CACHE = new Map();
|
|
718
|
+
const PROBE_TRACE_ENABLED = (() => {
|
|
719
|
+
const raw = String(process.env.GUARDEX_PROBE_TRACE || '').trim().toLowerCase();
|
|
720
|
+
return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
|
|
721
|
+
})();
|
|
722
|
+
const PROBE_CACHE_DISABLED = (() => {
|
|
723
|
+
const raw = String(process.env.GUARDEX_PROBE_CACHE || '').trim().toLowerCase();
|
|
724
|
+
return raw === '0' || raw === 'false' || raw === 'no' || raw === 'off';
|
|
725
|
+
})();
|
|
726
|
+
|
|
727
|
+
// Env vars that, if they differ between calls, must invalidate cache (because
|
|
728
|
+
// they change git/gh's actual answer). Most env doesn't matter for read probes
|
|
729
|
+
// so we deliberately key only on a small slice to keep cache hits high.
|
|
730
|
+
const PROBE_CACHE_ENV_KEYS = [
|
|
731
|
+
'GIT_DIR',
|
|
732
|
+
'GIT_WORK_TREE',
|
|
733
|
+
'GIT_COMMON_DIR',
|
|
734
|
+
'GIT_INDEX_FILE',
|
|
735
|
+
'GITHUB_TOKEN',
|
|
736
|
+
'GH_TOKEN',
|
|
737
|
+
'GH_HOST',
|
|
738
|
+
];
|
|
739
|
+
|
|
740
|
+
function envSubsetKey(envOverride) {
|
|
741
|
+
const env = envOverride || process.env;
|
|
742
|
+
const parts = [];
|
|
743
|
+
for (const key of PROBE_CACHE_ENV_KEYS) {
|
|
744
|
+
if (env[key] !== undefined) parts.push(`${key}=${env[key]}`);
|
|
745
|
+
}
|
|
746
|
+
return parts.join('');
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Strip a leading `-C <path>` (or `-c key=value`) pair from git args so we
|
|
750
|
+
// look at the real verb after global options.
|
|
751
|
+
function gitVerbAndRest(args) {
|
|
752
|
+
if (!Array.isArray(args) || args.length === 0) return { verb: '', rest: [] };
|
|
753
|
+
let i = 0;
|
|
754
|
+
while (i < args.length) {
|
|
755
|
+
const a = args[i];
|
|
756
|
+
if (a === '-C' && i + 1 < args.length) {
|
|
757
|
+
i += 2;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (a === '-c' && i + 1 < args.length) {
|
|
761
|
+
i += 2;
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
if (typeof a === 'string' && a.startsWith('-c') && a !== '-c') {
|
|
765
|
+
i += 1;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
return { verb: args[i] || '', rest: args.slice(i + 1) };
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// A git command is cacheable only if its answer is invariant for the lifetime
|
|
774
|
+
// of the process under our own commands' behavior. Many git "reads"
|
|
775
|
+
// (`status`, `diff`, `for-each-ref`, `rev-list`, `ls-files`, `worktree list`,
|
|
776
|
+
// `branch --show-current`, `merge-base --is-ancestor`) can change mid-run
|
|
777
|
+
// because gx itself writes (commits, branch ops, worktree add/remove, config
|
|
778
|
+
// writes). Caching those would feed callers stale answers and break command
|
|
779
|
+
// flows. The narrow allowlist below intentionally covers only:
|
|
780
|
+
// * filesystem geometry that git itself treats as fixed for a given repo
|
|
781
|
+
// checkout (`rev-parse --show-toplevel|--git-common-dir|--git-dir|
|
|
782
|
+
// --show-cdup|--show-superproject-working-tree|--is-inside-work-tree|
|
|
783
|
+
// --is-bare-repository`)
|
|
784
|
+
// * tool version banners (`--version`)
|
|
785
|
+
// Everything else falls through to a real spawn each time.
|
|
786
|
+
function gitIsCacheableRead(args) {
|
|
787
|
+
const { verb, rest } = gitVerbAndRest(args);
|
|
788
|
+
if (!verb) return false;
|
|
789
|
+
|
|
790
|
+
if (verb === '--version' || verb === 'version') return true;
|
|
791
|
+
|
|
792
|
+
if (verb === 'rev-parse') {
|
|
793
|
+
// Allow ONLY the geometry-probe forms whose answer never depends on the
|
|
794
|
+
// working-tree, ref state, or config. Concrete ref resolution
|
|
795
|
+
// (`rev-parse HEAD`, `rev-parse --verify <ref>`) is intentionally NOT
|
|
796
|
+
// cached because gx writes refs.
|
|
797
|
+
for (const a of rest) {
|
|
798
|
+
if (typeof a !== 'string') continue;
|
|
799
|
+
if (a.startsWith('-')) {
|
|
800
|
+
if (
|
|
801
|
+
a === '--show-toplevel' ||
|
|
802
|
+
a === '--git-common-dir' ||
|
|
803
|
+
a === '--git-dir' ||
|
|
804
|
+
a === '--show-cdup' ||
|
|
805
|
+
a === '--show-superproject-working-tree' ||
|
|
806
|
+
a === '--is-inside-work-tree' ||
|
|
807
|
+
a === '--is-inside-git-dir' ||
|
|
808
|
+
a === '--is-bare-repository' ||
|
|
809
|
+
a === '--show-prefix'
|
|
810
|
+
) {
|
|
811
|
+
// continue scanning to make sure no ref-probe is also present
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
// Any other flag (--verify, --short, --abbrev-ref, etc.) means we're
|
|
815
|
+
// resolving a ref, which is mutable.
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
// Bare positional (e.g. a ref name) -> ref resolution, mutable.
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
return rest.length > 0;
|
|
822
|
+
}
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// gh probes that ARE safe to cache within a single process: only the version
|
|
827
|
+
// banner. Auth state can change (login/logout in another shell), PR state
|
|
828
|
+
// can change (a merge can land mid-poll). The instruction's allowlist
|
|
829
|
+
// included `gh auth status`, `gh api -X GET`, `gh pr view --json`, etc.;
|
|
830
|
+
// those are read-only from gh's side but their underlying truth shifts under
|
|
831
|
+
// gx's own writes (`gh pr create`, `gh pr merge`, `gh auth refresh`, server
|
|
832
|
+
// state). Within a single doctor sweep we explicitly want to re-query auth
|
|
833
|
+
// and PR state, so caching them would mask freshly-changed reality.
|
|
834
|
+
function ghIsCacheableRead(args) {
|
|
835
|
+
if (!Array.isArray(args) || args.length === 0) return false;
|
|
836
|
+
const verb = args[0] || '';
|
|
837
|
+
if (verb === '--version' || verb === 'version') return true;
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function isCacheableSpawn(cmd, args, options) {
|
|
842
|
+
if (PROBE_CACHE_DISABLED) return false;
|
|
843
|
+
if (!cmd || typeof cmd !== 'string') return false;
|
|
844
|
+
if (options && options.input !== undefined && options.input !== null) return false;
|
|
845
|
+
// Pass-through if stdio is anything other than fully pipe/ignore (callers
|
|
846
|
+
// that inherit stdio need the real child to print, not a cached payload).
|
|
847
|
+
if (options && options.stdio && options.stdio !== 'pipe' && options.stdio !== 'ignore') {
|
|
848
|
+
if (Array.isArray(options.stdio)) {
|
|
849
|
+
for (const leg of options.stdio) {
|
|
850
|
+
if (leg && leg !== 'pipe' && leg !== 'ignore' && leg !== null) return false;
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const base = path.basename(cmd);
|
|
857
|
+
if (base === 'git') return gitIsCacheableRead(args || []);
|
|
858
|
+
if (base === 'gh' || base === 'ghx') return ghIsCacheableRead(args || []);
|
|
859
|
+
if (base === 'which' || base === 'command' || base === 'type') {
|
|
860
|
+
return Array.isArray(args) && args.length >= 1;
|
|
861
|
+
}
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function probeTrace(prefix, cmd, args) {
|
|
866
|
+
if (!PROBE_TRACE_ENABLED) return;
|
|
867
|
+
try {
|
|
868
|
+
process.stderr.write(`[${prefix}] ${cmd} ${(args || []).join(' ')}\n`);
|
|
869
|
+
} catch {
|
|
870
|
+
// Tracing must never break the probe.
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function cachedSpawn(cmd, args, options) {
|
|
875
|
+
const cacheable = isCacheableSpawn(cmd, args, options);
|
|
876
|
+
if (!cacheable) {
|
|
877
|
+
probeTrace('probe', cmd, args);
|
|
878
|
+
return cp.spawnSync(cmd, args, options);
|
|
879
|
+
}
|
|
880
|
+
const cwdKey = (options && options.cwd) || process.cwd();
|
|
881
|
+
const envKey = options && options.env
|
|
882
|
+
? envSubsetKey({ ...process.env, ...options.env })
|
|
883
|
+
: envSubsetKey(process.env);
|
|
884
|
+
const key = `${cmd} ${JSON.stringify(args || [])} ${cwdKey} ${envKey}`;
|
|
885
|
+
const cached = PROBE_CACHE.get(key);
|
|
886
|
+
if (cached) {
|
|
887
|
+
probeTrace('probe-hit', cmd, args);
|
|
888
|
+
// Clone so callers that mutate the result don't poison the cache.
|
|
889
|
+
return {
|
|
890
|
+
pid: cached.pid,
|
|
891
|
+
status: cached.status,
|
|
892
|
+
signal: cached.signal,
|
|
893
|
+
stdout: cached.stdout,
|
|
894
|
+
stderr: cached.stderr,
|
|
895
|
+
output: cached.output ? cached.output.slice() : cached.output,
|
|
896
|
+
error: cached.error,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
probeTrace('probe', cmd, args);
|
|
900
|
+
const result = cp.spawnSync(cmd, args, options);
|
|
901
|
+
// Don't cache spawn errors (ENOENT etc.) — they may resolve on retry with a
|
|
902
|
+
// different binary path.
|
|
903
|
+
if (result && result.error) {
|
|
904
|
+
return result;
|
|
905
|
+
}
|
|
906
|
+
PROBE_CACHE.set(key, {
|
|
907
|
+
pid: result && result.pid,
|
|
908
|
+
status: result && result.status,
|
|
909
|
+
signal: result && result.signal,
|
|
910
|
+
stdout: result && result.stdout,
|
|
911
|
+
stderr: result && result.stderr,
|
|
912
|
+
output: result && result.output,
|
|
913
|
+
error: result && result.error,
|
|
914
|
+
});
|
|
915
|
+
return result;
|
|
916
|
+
}
|
|
917
|
+
|
|
661
918
|
module.exports = {
|
|
662
919
|
fs,
|
|
663
920
|
os,
|
|
664
921
|
path,
|
|
665
922
|
cp,
|
|
923
|
+
cachedSpawn,
|
|
666
924
|
PACKAGE_ROOT,
|
|
667
925
|
CLI_ENTRY_PATH,
|
|
668
926
|
packageJsonPath,
|
|
@@ -680,6 +938,7 @@ module.exports = {
|
|
|
680
938
|
GLOBAL_TOOLCHAIN_SERVICES,
|
|
681
939
|
GLOBAL_TOOLCHAIN_PACKAGES,
|
|
682
940
|
OPTIONAL_LOCAL_COMPANION_TOOLS,
|
|
941
|
+
resolveGithubCliBin,
|
|
683
942
|
GH_BIN,
|
|
684
943
|
REQUIRED_SYSTEM_TOOLS,
|
|
685
944
|
MAINTAINER_RELEASE_REPO,
|
|
@@ -715,6 +974,8 @@ module.exports = {
|
|
|
715
974
|
AGENTS_BOTS_STATE_RELATIVE,
|
|
716
975
|
AGENTS_MARKER_START,
|
|
717
976
|
AGENTS_MARKER_END,
|
|
977
|
+
MONOREPO_MARKER_START,
|
|
978
|
+
MONOREPO_MARKER_END,
|
|
718
979
|
GITIGNORE_MARKER_START,
|
|
719
980
|
GITIGNORE_MARKER_END,
|
|
720
981
|
CODEX_WORKTREE_RELATIVE_DIR,
|
package/src/core/runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
fs,
|
|
3
3
|
path,
|
|
4
|
+
cachedSpawn,
|
|
4
5
|
CLI_ENTRY_PATH,
|
|
5
6
|
PACKAGE_SCRIPT_ASSETS,
|
|
6
7
|
} = require('../context');
|
|
@@ -13,8 +14,12 @@ function requireValue(rawArgs, index, flagName) {
|
|
|
13
14
|
return value;
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
// Route reads through the process-scoped probe cache. cachedSpawn caches ONLY a
|
|
18
|
+
// strict allowlist (git geometry probes, git/gh `--version`, `which`) and falls
|
|
19
|
+
// through to a real spawn for everything else — writes, ref resolution, npm,
|
|
20
|
+
// gh auth/pr — so observable behavior is unchanged, only redundant probes drop.
|
|
16
21
|
function run(cmd, args, options = {}) {
|
|
17
|
-
return
|
|
22
|
+
return cachedSpawn(cmd, args, {
|
|
18
23
|
encoding: 'utf8',
|
|
19
24
|
stdio: options.stdio || 'pipe',
|
|
20
25
|
cwd: options.cwd,
|
package/src/doctor/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const {
|
|
2
2
|
fs,
|
|
3
3
|
path,
|
|
4
|
+
cachedSpawn,
|
|
4
5
|
TOOL_NAME,
|
|
5
6
|
SHORT_TOOL_NAME,
|
|
7
|
+
GH_BIN,
|
|
6
8
|
LOCK_FILE_RELATIVE,
|
|
7
9
|
REQUIRED_MANAGED_REPO_FILES,
|
|
8
10
|
OMX_SCAFFOLD_DIRECTORIES,
|
|
@@ -10,7 +12,23 @@ const {
|
|
|
10
12
|
AGENT_WORKTREE_RELATIVE_DIRS,
|
|
11
13
|
defaultAgentWorktreeRelativeDir,
|
|
12
14
|
} = require('../context');
|
|
13
|
-
const {
|
|
15
|
+
const { runPackageAsset } = require('../core/runtime');
|
|
16
|
+
|
|
17
|
+
// Route doctor probe-running calls through the process-scoped probe cache.
|
|
18
|
+
// cachedSpawn falls through to cp.spawnSync for any non-allowlisted call
|
|
19
|
+
// (git commit/push/stash/checkout, gh auth login, etc.), so writes are never
|
|
20
|
+
// cached. Doctor fires the same read questions many times within one run
|
|
21
|
+
// (current branch, remote URL, worktree list, gh auth status) — caching
|
|
22
|
+
// those is a pure perf win.
|
|
23
|
+
function run(cmd, args, options = {}) {
|
|
24
|
+
return cachedSpawn(cmd, args, {
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
stdio: options.stdio || 'pipe',
|
|
27
|
+
cwd: options.cwd,
|
|
28
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
29
|
+
timeout: options.timeout,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
14
32
|
const {
|
|
15
33
|
currentBranchName,
|
|
16
34
|
gitRefExists,
|
|
@@ -30,7 +48,7 @@ const {
|
|
|
30
48
|
cleanupProtectedBaseSandbox,
|
|
31
49
|
} = require('../sandbox');
|
|
32
50
|
const { ensureOmxScaffold, configureHooks } = require('../scaffold');
|
|
33
|
-
const { detectRecoverableAutoFinishConflict, printAutoFinishSummary } = require('../output');
|
|
51
|
+
const { detectRecoverableAutoFinishConflict, printAutoFinishSummary, isTerseMode } = require('../output');
|
|
34
52
|
const { autoCommitWorktreeForFinish } = require('../finish');
|
|
35
53
|
|
|
36
54
|
/**
|
|
@@ -100,6 +118,7 @@ function buildSandboxDoctorArgs(options, sandboxTarget) {
|
|
|
100
118
|
if (options.skipAgents) args.push('--skip-agents');
|
|
101
119
|
if (options.skipPackageJson) args.push('--skip-package-json');
|
|
102
120
|
if (options.skipGitignore) args.push('--no-gitignore');
|
|
121
|
+
if (options.contract) args.push('--contract');
|
|
103
122
|
if (!options.dropStaleLocks) args.push('--keep-stale-locks');
|
|
104
123
|
args.push(options.waitForMerge ? '--wait-for-merge' : '--no-wait-for-merge');
|
|
105
124
|
if (options.verboseAutoFinish) args.push('--verbose-auto-finish');
|
|
@@ -442,7 +461,7 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
|
|
|
442
461
|
};
|
|
443
462
|
}
|
|
444
463
|
|
|
445
|
-
const ghBin =
|
|
464
|
+
const ghBin = GH_BIN;
|
|
446
465
|
if (!isCommandAvailable(ghBin)) {
|
|
447
466
|
return {
|
|
448
467
|
status: 'skipped',
|
|
@@ -890,7 +909,7 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
890
909
|
|
|
891
910
|
const originAvailable = hasOriginRemote(repoRoot);
|
|
892
911
|
const explicitGhBin = Boolean(String(process.env.GUARDEX_GH_BIN || '').trim());
|
|
893
|
-
const ghBin =
|
|
912
|
+
const ghBin = GH_BIN;
|
|
894
913
|
const ghAvailable =
|
|
895
914
|
originAvailable &&
|
|
896
915
|
(explicitGhBin || originRemoteLooksLikeGithub(repoRoot)) &&
|
|
@@ -1151,6 +1170,7 @@ function emitDoctorSandboxJsonOutput(nestedResult, execution) {
|
|
|
1151
1170
|
}
|
|
1152
1171
|
|
|
1153
1172
|
function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult, nestedResult, execution) {
|
|
1173
|
+
const terse = isTerseMode();
|
|
1154
1174
|
console.log(
|
|
1155
1175
|
`[${TOOL_NAME}] doctor detected protected branch '${blocked.branch}'. ` +
|
|
1156
1176
|
`Running repairs in sandbox branch '${metadata.branch || 'agent/<auto>'}'.`,
|
|
@@ -1163,6 +1183,10 @@ function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult,
|
|
|
1163
1183
|
return;
|
|
1164
1184
|
}
|
|
1165
1185
|
|
|
1186
|
+
// Terse mode: drop "[OK] X skipped because of Y" / "already in sync"
|
|
1187
|
+
// confirmations. Keep committed/failed/pending/merged states verbose so
|
|
1188
|
+
// operators still see action-required hints, PR URLs, branch names, and
|
|
1189
|
+
// file paths.
|
|
1166
1190
|
if (execution.autoCommit.status === 'committed') {
|
|
1167
1191
|
console.log(
|
|
1168
1192
|
`[${TOOL_NAME}] Auto-committed doctor repairs in sandbox branch '${metadata.branch}'.`,
|
|
@@ -1171,22 +1195,24 @@ function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult,
|
|
|
1171
1195
|
console.log(`[${TOOL_NAME}] Doctor sandbox auto-commit failed; branch left for manual follow-up.`);
|
|
1172
1196
|
if (execution.autoCommit.stdout) process.stdout.write(execution.autoCommit.stdout);
|
|
1173
1197
|
if (execution.autoCommit.stderr) process.stderr.write(execution.autoCommit.stderr);
|
|
1174
|
-
} else {
|
|
1198
|
+
} else if (!terse) {
|
|
1175
1199
|
console.log(`[${TOOL_NAME}] Doctor sandbox auto-commit skipped: ${execution.autoCommit.note}.`);
|
|
1176
1200
|
}
|
|
1177
1201
|
|
|
1178
1202
|
if (execution.protectedBaseRepairSync.status === 'merged') {
|
|
1179
1203
|
console.log(`[${TOOL_NAME}] Fast-forwarded tracked doctor repairs into the protected branch workspace.`);
|
|
1180
|
-
} else if (execution.protectedBaseRepairSync.status === 'unchanged') {
|
|
1181
|
-
console.log(`[${TOOL_NAME}] Protected branch workspace already had the tracked doctor repairs.`);
|
|
1182
1204
|
} else if (execution.protectedBaseRepairSync.status === 'would-merge') {
|
|
1183
1205
|
console.log(`[${TOOL_NAME}] Dry run: would fast-forward tracked doctor repairs into the protected branch workspace.`);
|
|
1184
1206
|
} else if (execution.protectedBaseRepairSync.status === 'failed') {
|
|
1185
1207
|
console.log(`[${TOOL_NAME}] Protected branch tracked repair merge failed: ${execution.protectedBaseRepairSync.note}.`);
|
|
1186
1208
|
if (execution.protectedBaseRepairSync.stdout) process.stdout.write(execution.protectedBaseRepairSync.stdout);
|
|
1187
1209
|
if (execution.protectedBaseRepairSync.stderr) process.stderr.write(execution.protectedBaseRepairSync.stderr);
|
|
1188
|
-
} else {
|
|
1189
|
-
|
|
1210
|
+
} else if (!terse) {
|
|
1211
|
+
if (execution.protectedBaseRepairSync.status === 'unchanged') {
|
|
1212
|
+
console.log(`[${TOOL_NAME}] Protected branch workspace already had the tracked doctor repairs.`);
|
|
1213
|
+
} else {
|
|
1214
|
+
console.log(`[${TOOL_NAME}] Protected branch tracked repair merge skipped: ${execution.protectedBaseRepairSync.note}.`);
|
|
1215
|
+
}
|
|
1190
1216
|
}
|
|
1191
1217
|
|
|
1192
1218
|
if (execution.lockSync.status === 'synced') {
|
|
@@ -1194,8 +1220,10 @@ function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult,
|
|
|
1194
1220
|
`[${TOOL_NAME}] Synced repaired lock registry back to protected branch workspace (${LOCK_FILE_RELATIVE}).`,
|
|
1195
1221
|
);
|
|
1196
1222
|
} else if (execution.lockSync.status === 'unchanged') {
|
|
1223
|
+
// Kept verbose in terse mode too: downstream consumers (and tests) rely
|
|
1224
|
+
// on seeing the lock-registry sync stage reach a terminal state line.
|
|
1197
1225
|
console.log(`[${TOOL_NAME}] Lock registry already synced in protected branch workspace.`);
|
|
1198
|
-
} else {
|
|
1226
|
+
} else if (!terse) {
|
|
1199
1227
|
console.log(`[${TOOL_NAME}] Lock registry sync skipped: ${execution.lockSync.note}.`);
|
|
1200
1228
|
}
|
|
1201
1229
|
|
|
@@ -1216,7 +1244,7 @@ function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult,
|
|
|
1216
1244
|
console.log(`[${TOOL_NAME}] Auto-finish flow failed for sandbox branch '${metadata.branch}'.`);
|
|
1217
1245
|
if (execution.finish.stdout) process.stdout.write(execution.finish.stdout);
|
|
1218
1246
|
if (execution.finish.stderr) process.stderr.write(execution.finish.stderr);
|
|
1219
|
-
} else {
|
|
1247
|
+
} else if (!terse) {
|
|
1220
1248
|
console.log(`[${TOOL_NAME}] Auto-finish skipped: ${execution.finish.note}.`);
|
|
1221
1249
|
}
|
|
1222
1250
|
|
|
@@ -1226,12 +1254,14 @@ function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult,
|
|
|
1226
1254
|
});
|
|
1227
1255
|
if (execution.omxScaffoldSync.status === 'synced') {
|
|
1228
1256
|
console.log(`[${TOOL_NAME}] Synced .omx scaffold back to protected branch workspace.`);
|
|
1229
|
-
} else if (execution.omxScaffoldSync.status === 'unchanged') {
|
|
1230
|
-
console.log(`[${TOOL_NAME}] .omx scaffold already aligned in protected branch workspace.`);
|
|
1231
1257
|
} else if (execution.omxScaffoldSync.status === 'would-sync') {
|
|
1232
1258
|
console.log(`[${TOOL_NAME}] Dry run: would sync .omx scaffold back to protected branch workspace.`);
|
|
1233
|
-
} else {
|
|
1234
|
-
|
|
1259
|
+
} else if (!terse) {
|
|
1260
|
+
if (execution.omxScaffoldSync.status === 'unchanged') {
|
|
1261
|
+
console.log(`[${TOOL_NAME}] .omx scaffold already aligned in protected branch workspace.`);
|
|
1262
|
+
} else {
|
|
1263
|
+
console.log(`[${TOOL_NAME}] .omx scaffold sync skipped: ${execution.omxScaffoldSync.note}.`);
|
|
1264
|
+
}
|
|
1235
1265
|
}
|
|
1236
1266
|
}
|
|
1237
1267
|
|