@imdeadpool/guardex 7.0.20 → 7.0.21
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 +44 -12
- package/package.json +1 -1
- package/src/cli/args.js +804 -2
- package/src/cli/main.js +173 -2328
- package/src/context.js +17 -3
- package/src/git/index.js +42 -32
- package/src/scaffold/index.js +1 -22
- package/templates/scripts/codex-agent.sh +14 -2
- package/templates/vscode/guardex-active-agents/README.md +1 -1
- package/templates/vscode/guardex-active-agents/extension.js +38 -7
- package/templates/vscode/guardex-active-agents/package.json +2 -0
- package/templates/vscode/guardex-active-agents/session-schema.js +233 -29
package/src/cli/main.js
CHANGED
|
@@ -1,364 +1,132 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const os = require('node:os');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const cp = require('node:child_process');
|
|
7
3
|
const hooksModule = require('../hooks');
|
|
8
4
|
const sandboxModule = require('../sandbox');
|
|
9
5
|
const toolchainModule = require('../toolchain');
|
|
10
6
|
const finishModule = require('../finish');
|
|
7
|
+
const {
|
|
8
|
+
fs,
|
|
9
|
+
path,
|
|
10
|
+
cp,
|
|
11
|
+
packageJson,
|
|
12
|
+
TOOL_NAME,
|
|
13
|
+
SHORT_TOOL_NAME,
|
|
14
|
+
OPENSPEC_PACKAGE,
|
|
15
|
+
NPX_BIN,
|
|
16
|
+
GUARDEX_HOME_DIR,
|
|
17
|
+
GLOBAL_TOOLCHAIN_SERVICES,
|
|
18
|
+
GLOBAL_TOOLCHAIN_PACKAGES,
|
|
19
|
+
OPTIONAL_LOCAL_COMPANION_TOOLS,
|
|
20
|
+
GH_BIN,
|
|
21
|
+
REQUIRED_SYSTEM_TOOLS,
|
|
22
|
+
MAINTAINER_RELEASE_REPO,
|
|
23
|
+
NPM_BIN,
|
|
24
|
+
OPENSPEC_BIN,
|
|
25
|
+
SCORECARD_BIN,
|
|
26
|
+
GIT_PROTECTED_BRANCHES_KEY,
|
|
27
|
+
GIT_BASE_BRANCH_KEY,
|
|
28
|
+
GIT_SYNC_STRATEGY_KEY,
|
|
29
|
+
GUARDEX_REPO_TOGGLE_ENV,
|
|
30
|
+
DEFAULT_PROTECTED_BRANCHES,
|
|
31
|
+
DEFAULT_BASE_BRANCH,
|
|
32
|
+
DEFAULT_SYNC_STRATEGY,
|
|
33
|
+
COMPOSE_HINT_FILES,
|
|
34
|
+
TEMPLATE_ROOT,
|
|
35
|
+
HOOK_NAMES,
|
|
36
|
+
TEMPLATE_FILES,
|
|
37
|
+
LEGACY_WORKFLOW_SHIM_SPECS,
|
|
38
|
+
LEGACY_MANAGED_REPO_FILES,
|
|
39
|
+
REQUIRED_MANAGED_REPO_FILES,
|
|
40
|
+
LEGACY_MANAGED_PACKAGE_SCRIPTS,
|
|
41
|
+
PACKAGE_SCRIPT_ASSETS,
|
|
42
|
+
USER_LEVEL_SKILL_ASSETS,
|
|
43
|
+
EXECUTABLE_RELATIVE_PATHS,
|
|
44
|
+
CRITICAL_GUARDRAIL_PATHS,
|
|
45
|
+
LOCK_FILE_RELATIVE,
|
|
46
|
+
AGENTS_BOTS_STATE_RELATIVE,
|
|
47
|
+
AGENTS_MARKER_START,
|
|
48
|
+
AGENTS_MARKER_END,
|
|
49
|
+
GITIGNORE_MARKER_START,
|
|
50
|
+
GITIGNORE_MARKER_END,
|
|
51
|
+
SHARED_VSCODE_SETTINGS_RELATIVE,
|
|
52
|
+
REPO_SCAN_IGNORED_FOLDERS_SETTING,
|
|
53
|
+
AGENT_WORKTREE_RELATIVE_DIRS,
|
|
54
|
+
MANAGED_REPO_SCAN_IGNORED_FOLDERS,
|
|
55
|
+
MANAGED_GITIGNORE_PATHS,
|
|
56
|
+
REPO_SCAFFOLD_DIRECTORIES,
|
|
57
|
+
OMX_SCAFFOLD_DIRECTORIES,
|
|
58
|
+
OMX_SCAFFOLD_FILES,
|
|
59
|
+
TARGETED_FORCEABLE_MANAGED_PATHS,
|
|
60
|
+
DEPRECATED_COMMAND_ALIASES,
|
|
61
|
+
envFlagIsTruthy,
|
|
62
|
+
defaultAgentWorktreeRelativeDir,
|
|
63
|
+
AI_SETUP_PROMPT,
|
|
64
|
+
AI_SETUP_COMMANDS,
|
|
65
|
+
SCORECARD_RISK_BY_CHECK,
|
|
66
|
+
} = require('../context');
|
|
67
|
+
const {
|
|
68
|
+
gitRun,
|
|
69
|
+
resolveRepoRoot,
|
|
70
|
+
isGitRepo,
|
|
71
|
+
discoverNestedGitRepos,
|
|
72
|
+
} = require('../git');
|
|
73
|
+
const {
|
|
74
|
+
run,
|
|
75
|
+
extractTargetedArgs,
|
|
76
|
+
packageAssetEnv,
|
|
77
|
+
runPackageAsset,
|
|
78
|
+
runReviewBotCommand,
|
|
79
|
+
invokePackageAsset,
|
|
80
|
+
} = require('../core/runtime');
|
|
81
|
+
const {
|
|
82
|
+
normalizeManagedForcePath,
|
|
83
|
+
parseCommonArgs,
|
|
84
|
+
parseSetupArgs,
|
|
85
|
+
parseDoctorArgs,
|
|
86
|
+
parseTargetFlag,
|
|
87
|
+
parseReviewArgs,
|
|
88
|
+
parseAgentsArgs,
|
|
89
|
+
parseReportArgs,
|
|
90
|
+
parseSyncArgs,
|
|
91
|
+
parseCleanupArgs,
|
|
92
|
+
parseMergeArgs,
|
|
93
|
+
parseFinishArgs,
|
|
94
|
+
} = require('./args');
|
|
95
|
+
const {
|
|
96
|
+
maybeSuggestCommand,
|
|
97
|
+
normalizeCommandOrThrow,
|
|
98
|
+
warnDeprecatedAlias,
|
|
99
|
+
extractFlag,
|
|
100
|
+
} = require('./dispatch');
|
|
101
|
+
const {
|
|
102
|
+
runtimeVersion,
|
|
103
|
+
colorize,
|
|
104
|
+
colorizeDoctorOutput,
|
|
105
|
+
statusDot,
|
|
106
|
+
printToolLogsSummary,
|
|
107
|
+
usage,
|
|
108
|
+
formatElapsedDuration,
|
|
109
|
+
compactAutoFinishPathSegments,
|
|
110
|
+
detectRecoverableAutoFinishConflict,
|
|
111
|
+
printAutoFinishSummary,
|
|
112
|
+
} = require('../output');
|
|
113
|
+
const {
|
|
114
|
+
toDestinationPath,
|
|
115
|
+
ensureParentDir,
|
|
116
|
+
ensureExecutable,
|
|
117
|
+
isCriticalGuardrailPath,
|
|
118
|
+
shellSingleQuote,
|
|
119
|
+
renderShellDispatchShim,
|
|
120
|
+
renderPythonDispatchShim,
|
|
121
|
+
managedForceConflictMessage,
|
|
122
|
+
printOperations,
|
|
123
|
+
printStandaloneOperations,
|
|
124
|
+
} = require('../scaffold');
|
|
11
125
|
|
|
12
126
|
let sandboxApi;
|
|
13
127
|
let toolchainApi;
|
|
14
128
|
let finishApi;
|
|
15
129
|
|
|
16
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '../..');
|
|
17
|
-
const CLI_ENTRY_PATH = path.join(PACKAGE_ROOT, 'bin', 'multiagent-safety.js');
|
|
18
|
-
const packageJsonPath = path.join(PACKAGE_ROOT, 'package.json');
|
|
19
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
20
|
-
|
|
21
|
-
const TOOL_NAME = 'gitguardex';
|
|
22
|
-
const SHORT_TOOL_NAME = 'gx';
|
|
23
|
-
if (!process.env.GUARDEX_CLI_ENTRY) {
|
|
24
|
-
process.env.GUARDEX_CLI_ENTRY = CLI_ENTRY_PATH;
|
|
25
|
-
}
|
|
26
|
-
if (!process.env.GUARDEX_NODE_BIN) {
|
|
27
|
-
process.env.GUARDEX_NODE_BIN = process.execPath;
|
|
28
|
-
}
|
|
29
|
-
const LEGACY_NAMES = ['guardex', 'multiagent-safety'];
|
|
30
|
-
const GLOBAL_INSTALL_COMMAND = `npm i -g ${packageJson.name}`;
|
|
31
|
-
const OPENSPEC_PACKAGE = '@fission-ai/openspec';
|
|
32
|
-
const OMC_PACKAGE = 'oh-my-claude-sisyphus';
|
|
33
|
-
const OMC_REPO_URL = 'https://github.com/Yeachan-Heo/oh-my-claudecode';
|
|
34
|
-
const CAVEMEM_PACKAGE = 'cavemem';
|
|
35
|
-
const NPX_BIN = process.env.GUARDEX_NPX_BIN || 'npx';
|
|
36
|
-
const GUARDEX_HOME_DIR = path.resolve(process.env.GUARDEX_HOME_DIR || os.homedir());
|
|
37
|
-
const GLOBAL_TOOLCHAIN_SERVICES = [
|
|
38
|
-
{ name: 'oh-my-codex', packageName: 'oh-my-codex' },
|
|
39
|
-
{
|
|
40
|
-
name: 'oh-my-claudecode',
|
|
41
|
-
packageName: OMC_PACKAGE,
|
|
42
|
-
dependencyUrl: OMC_REPO_URL,
|
|
43
|
-
},
|
|
44
|
-
{ name: OPENSPEC_PACKAGE, packageName: OPENSPEC_PACKAGE },
|
|
45
|
-
{ name: CAVEMEM_PACKAGE, packageName: CAVEMEM_PACKAGE },
|
|
46
|
-
{
|
|
47
|
-
name: '@imdeadpool/codex-account-switcher',
|
|
48
|
-
packageName: '@imdeadpool/codex-account-switcher',
|
|
49
|
-
},
|
|
50
|
-
];
|
|
51
|
-
const GLOBAL_TOOLCHAIN_PACKAGES = [
|
|
52
|
-
...GLOBAL_TOOLCHAIN_SERVICES.map((service) => service.packageName),
|
|
53
|
-
];
|
|
54
|
-
const OPTIONAL_LOCAL_COMPANION_TOOLS = [
|
|
55
|
-
{
|
|
56
|
-
name: 'cavekit',
|
|
57
|
-
candidatePaths: [
|
|
58
|
-
'.cavekit/plugin.json',
|
|
59
|
-
'.codex/local-marketplaces/cavekit/.agents/plugins/marketplace.json',
|
|
60
|
-
],
|
|
61
|
-
installCommand: `${NPX_BIN} skills add JuliusBrussee/cavekit`,
|
|
62
|
-
installArgs: ['skills', 'add', 'JuliusBrussee/cavekit'],
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'caveman',
|
|
66
|
-
candidatePaths: [
|
|
67
|
-
'.config/caveman/config.json',
|
|
68
|
-
'.cavekit/skills/caveman/SKILL.md',
|
|
69
|
-
],
|
|
70
|
-
installCommand: `${NPX_BIN} skills add JuliusBrussee/caveman`,
|
|
71
|
-
installArgs: ['skills', 'add', 'JuliusBrussee/caveman'],
|
|
72
|
-
},
|
|
73
|
-
];
|
|
74
|
-
const GH_BIN = process.env.GUARDEX_GH_BIN || 'gh';
|
|
75
|
-
const REQUIRED_SYSTEM_TOOLS = [
|
|
76
|
-
{
|
|
77
|
-
name: 'gh',
|
|
78
|
-
displayName: 'GitHub (gh)',
|
|
79
|
-
command: GH_BIN,
|
|
80
|
-
installHint: 'https://cli.github.com/',
|
|
81
|
-
},
|
|
82
|
-
];
|
|
83
|
-
const MAINTAINER_RELEASE_REPO = path.resolve(
|
|
84
|
-
process.env.GUARDEX_RELEASE_REPO || path.resolve(__dirname, '..'),
|
|
85
|
-
);
|
|
86
|
-
const NPM_BIN = process.env.GUARDEX_NPM_BIN || 'npm';
|
|
87
|
-
const OPENSPEC_BIN = process.env.GUARDEX_OPENSPEC_BIN || 'openspec';
|
|
88
|
-
const SCORECARD_BIN = process.env.GUARDEX_SCORECARD_BIN || 'scorecard';
|
|
89
|
-
const GIT_PROTECTED_BRANCHES_KEY = 'multiagent.protectedBranches';
|
|
90
|
-
const GIT_BASE_BRANCH_KEY = 'multiagent.baseBranch';
|
|
91
|
-
const GIT_SYNC_STRATEGY_KEY = 'multiagent.sync.strategy';
|
|
92
|
-
const GUARDEX_REPO_TOGGLE_ENV = 'GUARDEX_ON';
|
|
93
|
-
const DEFAULT_PROTECTED_BRANCHES = ['dev', 'main', 'master'];
|
|
94
|
-
const DEFAULT_BASE_BRANCH = 'dev';
|
|
95
|
-
const DEFAULT_SYNC_STRATEGY = 'rebase';
|
|
96
|
-
const DEFAULT_SHADOW_CLEANUP_IDLE_MINUTES = 60;
|
|
97
|
-
const COMPOSE_HINT_FILES = [
|
|
98
|
-
'docker-compose.yml',
|
|
99
|
-
'docker-compose.yaml',
|
|
100
|
-
'compose.yml',
|
|
101
|
-
'compose.yaml',
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
const TEMPLATE_ROOT = path.join(PACKAGE_ROOT, 'templates');
|
|
105
|
-
|
|
106
|
-
const HOOK_NAMES = ['pre-commit', 'pre-push', 'post-merge', 'post-checkout'];
|
|
107
|
-
|
|
108
|
-
const TEMPLATE_FILES = [
|
|
109
|
-
'scripts/agent-session-state.js',
|
|
110
|
-
'scripts/guardex-docker-loader.sh',
|
|
111
|
-
'scripts/guardex-env.sh',
|
|
112
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
113
|
-
'github/pull.yml.example',
|
|
114
|
-
'github/workflows/cr.yml',
|
|
115
|
-
'vscode/guardex-active-agents/package.json',
|
|
116
|
-
'vscode/guardex-active-agents/extension.js',
|
|
117
|
-
'vscode/guardex-active-agents/session-schema.js',
|
|
118
|
-
'vscode/guardex-active-agents/README.md',
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
const LEGACY_WORKFLOW_SHIM_SPECS = [
|
|
122
|
-
{ relativePath: 'scripts/agent-branch-start.sh', kind: 'shell', command: ['branch', 'start'] },
|
|
123
|
-
{ relativePath: 'scripts/agent-branch-finish.sh', kind: 'shell', command: ['branch', 'finish'] },
|
|
124
|
-
{ relativePath: 'scripts/agent-branch-merge.sh', kind: 'shell', command: ['branch', 'merge'] },
|
|
125
|
-
{ relativePath: 'scripts/codex-agent.sh', kind: 'shell', command: ['internal', 'run-shell', 'codexAgent'] },
|
|
126
|
-
{ relativePath: 'scripts/review-bot-watch.sh', kind: 'shell', command: ['internal', 'run-shell', 'reviewBot'] },
|
|
127
|
-
{ relativePath: 'scripts/agent-worktree-prune.sh', kind: 'shell', command: ['worktree', 'prune'] },
|
|
128
|
-
{ relativePath: 'scripts/agent-file-locks.py', kind: 'python', command: ['locks'] },
|
|
129
|
-
{ relativePath: 'scripts/openspec/init-plan-workspace.sh', kind: 'shell', command: ['internal', 'run-shell', 'planInit'] },
|
|
130
|
-
{ relativePath: 'scripts/openspec/init-change-workspace.sh', kind: 'shell', command: ['internal', 'run-shell', 'changeInit'] },
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
const LEGACY_WORKFLOW_SHIMS = LEGACY_WORKFLOW_SHIM_SPECS.map((entry) => entry.relativePath);
|
|
134
|
-
|
|
135
|
-
const MANAGED_TEMPLATE_DESTINATIONS = TEMPLATE_FILES.map((entry) => toDestinationPath(entry));
|
|
136
|
-
const MANAGED_TEMPLATE_SCRIPT_FILES = MANAGED_TEMPLATE_DESTINATIONS.filter((entry) =>
|
|
137
|
-
entry.startsWith('scripts/'),
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const LEGACY_MANAGED_REPO_FILES = [
|
|
141
|
-
...LEGACY_WORKFLOW_SHIMS,
|
|
142
|
-
'scripts/agent-session-state.js',
|
|
143
|
-
'scripts/guardex-docker-loader.sh',
|
|
144
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
145
|
-
'scripts/guardex-env.sh',
|
|
146
|
-
'scripts/install-agent-git-hooks.sh',
|
|
147
|
-
'.githooks/pre-commit',
|
|
148
|
-
'.githooks/pre-push',
|
|
149
|
-
'.githooks/post-merge',
|
|
150
|
-
'.githooks/post-checkout',
|
|
151
|
-
'.codex/skills/gitguardex/SKILL.md',
|
|
152
|
-
'.codex/skills/guardex-merge-skills-to-dev/SKILL.md',
|
|
153
|
-
'.claude/commands/gitguardex.md',
|
|
154
|
-
];
|
|
155
|
-
|
|
156
|
-
const REQUIRED_MANAGED_REPO_FILES = [
|
|
157
|
-
...MANAGED_TEMPLATE_DESTINATIONS,
|
|
158
|
-
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
159
|
-
'.omx/state/agent-file-locks.json',
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
const LEGACY_MANAGED_PACKAGE_SCRIPTS = {
|
|
163
|
-
'agent:codex': 'bash ./scripts/codex-agent.sh',
|
|
164
|
-
'agent:branch:start': 'bash ./scripts/agent-branch-start.sh',
|
|
165
|
-
'agent:branch:finish': 'bash ./scripts/agent-branch-finish.sh',
|
|
166
|
-
'agent:branch:merge': 'bash ./scripts/agent-branch-merge.sh',
|
|
167
|
-
'agent:cleanup': 'gx cleanup',
|
|
168
|
-
'agent:hooks:install': 'bash ./scripts/install-agent-git-hooks.sh',
|
|
169
|
-
'agent:locks:claim': 'python3 ./scripts/agent-file-locks.py claim',
|
|
170
|
-
'agent:locks:allow-delete': 'python3 ./scripts/agent-file-locks.py allow-delete',
|
|
171
|
-
'agent:locks:release': 'python3 ./scripts/agent-file-locks.py release',
|
|
172
|
-
'agent:locks:status': 'python3 ./scripts/agent-file-locks.py status',
|
|
173
|
-
'agent:plan:init': 'bash ./scripts/openspec/init-plan-workspace.sh',
|
|
174
|
-
'agent:change:init': 'bash ./scripts/openspec/init-change-workspace.sh',
|
|
175
|
-
'agent:protect:list': 'gx protect list',
|
|
176
|
-
'agent:branch:sync': 'gx sync',
|
|
177
|
-
'agent:branch:sync:check': 'gx sync --check',
|
|
178
|
-
'agent:safety:setup': 'gx setup',
|
|
179
|
-
'agent:safety:scan': 'gx status --strict',
|
|
180
|
-
'agent:safety:fix': 'gx setup --repair',
|
|
181
|
-
'agent:safety:doctor': 'gx doctor',
|
|
182
|
-
'agent:docker:load': 'bash ./scripts/guardex-docker-loader.sh',
|
|
183
|
-
'agent:review:watch': 'bash ./scripts/review-bot-watch.sh',
|
|
184
|
-
'agent:finish': 'gx finish --all',
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const PACKAGE_SCRIPT_ASSETS = {
|
|
188
|
-
branchStart: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-start.sh'),
|
|
189
|
-
branchFinish: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-finish.sh'),
|
|
190
|
-
branchMerge: path.join(TEMPLATE_ROOT, 'scripts', 'agent-branch-merge.sh'),
|
|
191
|
-
codexAgent: path.join(TEMPLATE_ROOT, 'scripts', 'codex-agent.sh'),
|
|
192
|
-
reviewBot: path.join(TEMPLATE_ROOT, 'scripts', 'review-bot-watch.sh'),
|
|
193
|
-
worktreePrune: path.join(TEMPLATE_ROOT, 'scripts', 'agent-worktree-prune.sh'),
|
|
194
|
-
lockTool: path.join(TEMPLATE_ROOT, 'scripts', 'agent-file-locks.py'),
|
|
195
|
-
planInit: path.join(TEMPLATE_ROOT, 'scripts', 'openspec', 'init-plan-workspace.sh'),
|
|
196
|
-
changeInit: path.join(TEMPLATE_ROOT, 'scripts', 'openspec', 'init-change-workspace.sh'),
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const USER_LEVEL_SKILL_ASSETS = [
|
|
200
|
-
{
|
|
201
|
-
source: path.join(TEMPLATE_ROOT, 'codex', 'skills', 'gitguardex', 'SKILL.md'),
|
|
202
|
-
destination: path.join('.codex', 'skills', 'gitguardex', 'SKILL.md'),
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
source: path.join(TEMPLATE_ROOT, 'codex', 'skills', 'guardex-merge-skills-to-dev', 'SKILL.md'),
|
|
206
|
-
destination: path.join('.codex', 'skills', 'guardex-merge-skills-to-dev', 'SKILL.md'),
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
source: path.join(TEMPLATE_ROOT, 'claude', 'commands', 'gitguardex.md'),
|
|
210
|
-
destination: path.join('.claude', 'commands', 'gitguardex.md'),
|
|
211
|
-
},
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
const EXECUTABLE_RELATIVE_PATHS = new Set([
|
|
215
|
-
...MANAGED_TEMPLATE_SCRIPT_FILES,
|
|
216
|
-
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
217
|
-
]);
|
|
218
|
-
|
|
219
|
-
const CRITICAL_GUARDRAIL_PATHS = new Set([
|
|
220
|
-
'AGENTS.md',
|
|
221
|
-
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
222
|
-
'scripts/guardex-env.sh',
|
|
223
|
-
]);
|
|
224
|
-
|
|
225
|
-
const LOCK_FILE_RELATIVE = '.omx/state/agent-file-locks.json';
|
|
226
|
-
const AGENTS_BOTS_STATE_RELATIVE = '.omx/state/agents-bots.json';
|
|
227
|
-
const AGENTS_MARKER_START = '<!-- multiagent-safety:START -->';
|
|
228
|
-
const AGENTS_MARKER_END = '<!-- multiagent-safety:END -->';
|
|
229
|
-
const GITIGNORE_MARKER_START = '# multiagent-safety:START';
|
|
230
|
-
const GITIGNORE_MARKER_END = '# multiagent-safety:END';
|
|
231
|
-
const CODEX_WORKTREE_RELATIVE_DIR = path.join('.omx', 'agent-worktrees');
|
|
232
|
-
const CLAUDE_WORKTREE_RELATIVE_DIR = path.join('.omc', 'agent-worktrees');
|
|
233
|
-
const SHARED_VSCODE_SETTINGS_RELATIVE = path.posix.join('.vscode', 'settings.json');
|
|
234
|
-
const REPO_SCAN_IGNORED_FOLDERS_SETTING = 'git.repositoryScanIgnoredFolders';
|
|
235
|
-
const AGENT_WORKTREE_RELATIVE_DIRS = [
|
|
236
|
-
CODEX_WORKTREE_RELATIVE_DIR,
|
|
237
|
-
CLAUDE_WORKTREE_RELATIVE_DIR,
|
|
238
|
-
];
|
|
239
|
-
const MANAGED_REPO_SCAN_IGNORED_FOLDERS = [
|
|
240
|
-
'.omx/agent-worktrees',
|
|
241
|
-
'**/.omx/agent-worktrees',
|
|
242
|
-
'.omc/agent-worktrees',
|
|
243
|
-
'**/.omc/agent-worktrees',
|
|
244
|
-
];
|
|
245
|
-
const MANAGED_GITIGNORE_PATHS = [
|
|
246
|
-
'.omx/',
|
|
247
|
-
'.omc/',
|
|
248
|
-
'!.vscode/',
|
|
249
|
-
'.vscode/*',
|
|
250
|
-
'!.vscode/settings.json',
|
|
251
|
-
'scripts/agent-session-state.js',
|
|
252
|
-
'scripts/guardex-docker-loader.sh',
|
|
253
|
-
'scripts/guardex-env.sh',
|
|
254
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
255
|
-
'.githooks',
|
|
256
|
-
'oh-my-codex/',
|
|
257
|
-
LOCK_FILE_RELATIVE,
|
|
258
|
-
];
|
|
259
|
-
const REPO_SCAFFOLD_DIRECTORIES = ['bin'];
|
|
260
|
-
const OMX_SCAFFOLD_DIRECTORIES = [
|
|
261
|
-
'.omx',
|
|
262
|
-
'.omx/state',
|
|
263
|
-
'.omx/logs',
|
|
264
|
-
'.omx/plans',
|
|
265
|
-
CODEX_WORKTREE_RELATIVE_DIR,
|
|
266
|
-
'.omc',
|
|
267
|
-
CLAUDE_WORKTREE_RELATIVE_DIR,
|
|
268
|
-
];
|
|
269
|
-
const OMX_SCAFFOLD_FILES = new Map([
|
|
270
|
-
['.omx/notepad.md', '\n\n## WORKING MEMORY\n'],
|
|
271
|
-
['.omx/project-memory.json', '{}\n'],
|
|
272
|
-
]);
|
|
273
|
-
const TARGETED_FORCEABLE_MANAGED_PATHS = new Set([
|
|
274
|
-
'AGENTS.md',
|
|
275
|
-
'.gitignore',
|
|
276
|
-
...Array.from(OMX_SCAFFOLD_FILES.keys()),
|
|
277
|
-
...REQUIRED_MANAGED_REPO_FILES,
|
|
278
|
-
...LEGACY_WORKFLOW_SHIMS,
|
|
279
|
-
]);
|
|
280
|
-
const COMMAND_TYPO_ALIASES = new Map([
|
|
281
|
-
['relaese', 'release'],
|
|
282
|
-
['realaese', 'release'],
|
|
283
|
-
['relase', 'release'],
|
|
284
|
-
['setpu', 'setup'],
|
|
285
|
-
['inti', 'init'],
|
|
286
|
-
['intsall', 'install'],
|
|
287
|
-
['docter', 'doctor'],
|
|
288
|
-
['doctro', 'doctor'],
|
|
289
|
-
['cleunup', 'cleanup'],
|
|
290
|
-
['scna', 'scan'],
|
|
291
|
-
]);
|
|
292
|
-
const SUGGESTIBLE_COMMANDS = [
|
|
293
|
-
'status',
|
|
294
|
-
'setup',
|
|
295
|
-
'doctor',
|
|
296
|
-
'branch',
|
|
297
|
-
'locks',
|
|
298
|
-
'worktree',
|
|
299
|
-
'hook',
|
|
300
|
-
'migrate',
|
|
301
|
-
'install-agent-skills',
|
|
302
|
-
'agents',
|
|
303
|
-
'merge',
|
|
304
|
-
'finish',
|
|
305
|
-
'report',
|
|
306
|
-
'protect',
|
|
307
|
-
'sync',
|
|
308
|
-
'cleanup',
|
|
309
|
-
'prompt',
|
|
310
|
-
'help',
|
|
311
|
-
'version',
|
|
312
|
-
// deprecated aliases still routable with a warning
|
|
313
|
-
'init',
|
|
314
|
-
'install',
|
|
315
|
-
'fix',
|
|
316
|
-
'scan',
|
|
317
|
-
'review',
|
|
318
|
-
'copy-prompt',
|
|
319
|
-
'copy-commands',
|
|
320
|
-
'print-agents-snippet',
|
|
321
|
-
'release',
|
|
322
|
-
];
|
|
323
|
-
const CLI_COMMAND_DESCRIPTIONS = [
|
|
324
|
-
['status', 'Show GitGuardex CLI + service health without modifying files'],
|
|
325
|
-
['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target)'],
|
|
326
|
-
['doctor', 'Repair drift + verify (auto-sandboxes on protected main)'],
|
|
327
|
-
['branch', 'CLI-owned branch workflow surface (start/finish/merge)'],
|
|
328
|
-
['locks', 'CLI-owned file lock surface (claim/allow-delete/release/status/validate)'],
|
|
329
|
-
['worktree', 'CLI-owned worktree cleanup surface (prune)'],
|
|
330
|
-
['hook', 'Hook dispatch/install surface used by managed shims'],
|
|
331
|
-
['migrate', 'Convert legacy repo-local installs to the zero-copy CLI-owned surface'],
|
|
332
|
-
['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
|
|
333
|
-
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
|
|
334
|
-
['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
|
|
335
|
-
['sync', 'Sync agent branches with origin/<base>'],
|
|
336
|
-
['finish', 'Commit + PR + merge completed agent branches (--all, --branch)'],
|
|
337
|
-
['cleanup', 'Prune merged/stale agent branches and worktrees'],
|
|
338
|
-
['release', 'Create or update the current GitHub release with README-generated notes'],
|
|
339
|
-
['agents', 'Start/stop repo-scoped review + cleanup bots'],
|
|
340
|
-
['prompt', 'Print AI setup checklist (--exec, --snippet)'],
|
|
341
|
-
['report', 'Security/safety reports (e.g. OpenSSF scorecard)'],
|
|
342
|
-
['help', 'Show this help output'],
|
|
343
|
-
['version', 'Print GitGuardex version'],
|
|
344
|
-
];
|
|
345
|
-
const DEPRECATED_COMMAND_ALIASES = new Map([
|
|
346
|
-
['init', { target: 'setup', hint: 'gx setup' }],
|
|
347
|
-
['install', { target: 'setup', hint: 'gx setup --install-only' }],
|
|
348
|
-
['fix', { target: 'setup', hint: 'gx setup --repair' }],
|
|
349
|
-
['scan', { target: 'status', hint: 'gx status --strict' }],
|
|
350
|
-
['copy-prompt', { target: 'prompt', hint: 'gx prompt' }],
|
|
351
|
-
['copy-commands', { target: 'prompt', hint: 'gx prompt --exec' }],
|
|
352
|
-
['print-agents-snippet', { target: 'prompt', hint: 'gx prompt --snippet' }],
|
|
353
|
-
['review', { target: 'agents', hint: 'gx agents start (runs review + cleanup)' }],
|
|
354
|
-
]);
|
|
355
|
-
const AGENT_BOT_DESCRIPTIONS = [
|
|
356
|
-
['agents', 'Start/stop review + cleanup bots for this repo'],
|
|
357
|
-
];
|
|
358
|
-
const DOCTOR_AUTO_FINISH_DETAIL_LIMIT = 6;
|
|
359
|
-
const DOCTOR_AUTO_FINISH_BRANCH_LABEL_MAX = 72;
|
|
360
|
-
const DOCTOR_AUTO_FINISH_MESSAGE_MAX = 160;
|
|
361
|
-
|
|
362
130
|
function getSandboxApi() {
|
|
363
131
|
if (!sandboxApi) {
|
|
364
132
|
sandboxApi = sandboxModule.createSandboxApi({
|
|
@@ -439,114 +207,6 @@ function getFinishApi() {
|
|
|
439
207
|
return finishApi;
|
|
440
208
|
}
|
|
441
209
|
|
|
442
|
-
function envFlagIsTruthy(raw) {
|
|
443
|
-
const lowered = String(raw || '').trim().toLowerCase();
|
|
444
|
-
return lowered === '1' || lowered === 'true' || lowered === 'yes' || lowered === 'on';
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function isClaudeCodeSession(env = process.env) {
|
|
448
|
-
return envFlagIsTruthy(env.CLAUDECODE) || Boolean(env.CLAUDE_CODE_SESSION_ID);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function defaultAgentWorktreeRelativeDir(env = process.env) {
|
|
452
|
-
return isClaudeCodeSession(env) ? CLAUDE_WORKTREE_RELATIVE_DIR : CODEX_WORKTREE_RELATIVE_DIR;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in this repo.
|
|
456
|
-
|
|
457
|
-
1) Install: ${GLOBAL_INSTALL_COMMAND} && gh --version
|
|
458
|
-
2) Bootstrap: gx setup
|
|
459
|
-
3) Repair: gx doctor
|
|
460
|
-
4) Task loop: gx branch start "<task>" "<agent>"
|
|
461
|
-
then gx locks claim --branch "<agent-branch>" <file...> -> gx branch finish
|
|
462
|
-
5) Integrate: gx merge --branch <agent-a> --branch <agent-b>
|
|
463
|
-
6) Finish: gx finish --all
|
|
464
|
-
7) Cleanup: gx cleanup
|
|
465
|
-
8) OpenSpec: /opsx:propose -> /opsx:apply -> /opsx:archive
|
|
466
|
-
9) Optional: gx protect add release staging
|
|
467
|
-
10) Optional: gx sync --check && gx sync
|
|
468
|
-
11) Review bot: install https://github.com/apps/cr-gpt + set OPENAI_API_KEY
|
|
469
|
-
12) Fork sync: install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml
|
|
470
|
-
`;
|
|
471
|
-
|
|
472
|
-
const AI_SETUP_COMMANDS = `${GLOBAL_INSTALL_COMMAND}
|
|
473
|
-
gh --version
|
|
474
|
-
gx setup
|
|
475
|
-
gx doctor
|
|
476
|
-
gx branch start "<task>" "<agent>"
|
|
477
|
-
gx locks claim --branch "<agent-branch>" <file...>
|
|
478
|
-
gx merge --branch "<agent-a>" --branch "<agent-b>"
|
|
479
|
-
gx finish --all
|
|
480
|
-
gx cleanup
|
|
481
|
-
gx protect add release staging
|
|
482
|
-
gx sync --check && gx sync
|
|
483
|
-
`;
|
|
484
|
-
|
|
485
|
-
const SCORECARD_RISK_BY_CHECK = {
|
|
486
|
-
'Dangerous-Workflow': 'Critical',
|
|
487
|
-
'Code-Review': 'High',
|
|
488
|
-
Maintained: 'High',
|
|
489
|
-
'Binary-Artifacts': 'High',
|
|
490
|
-
'Dependency-Update-Tool': 'High',
|
|
491
|
-
'Token-Permissions': 'High',
|
|
492
|
-
Vulnerabilities: 'High',
|
|
493
|
-
'Branch-Protection': 'High',
|
|
494
|
-
Fuzzing: 'Medium',
|
|
495
|
-
'Pinned-Dependencies': 'Medium',
|
|
496
|
-
SAST: 'Medium',
|
|
497
|
-
'Security-Policy': 'Medium',
|
|
498
|
-
'CII-Best-Practices': 'Low',
|
|
499
|
-
Contributors: 'Low',
|
|
500
|
-
License: 'Low',
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
function runtimeVersion() {
|
|
504
|
-
return `${packageJson.name}/${packageJson.version} ${process.platform}-${process.arch} node-${process.version}`;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
function supportsAnsiColors() {
|
|
508
|
-
const forced = String(process.env.FORCE_COLOR || '').trim().toLowerCase();
|
|
509
|
-
if (['0', 'false', 'no', 'off'].includes(forced)) {
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
if (forced.length > 0) {
|
|
513
|
-
return true;
|
|
514
|
-
}
|
|
515
|
-
if (process.env.NO_COLOR) {
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
return Boolean(process.stdout.isTTY) && process.env.TERM !== 'dumb';
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function colorize(text, colorCode) {
|
|
522
|
-
if (!supportsAnsiColors()) {
|
|
523
|
-
return text;
|
|
524
|
-
}
|
|
525
|
-
return `\u001B[${colorCode}m${text}\u001B[0m`;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function doctorOutputColorCode(status) {
|
|
529
|
-
const normalized = String(status || '').trim().toLowerCase();
|
|
530
|
-
if (['active', 'done', 'ok', 'safe', 'success'].includes(normalized)) {
|
|
531
|
-
return '32';
|
|
532
|
-
}
|
|
533
|
-
if (normalized === 'disabled') {
|
|
534
|
-
return '36';
|
|
535
|
-
}
|
|
536
|
-
if (['degraded', 'pending', 'skip', 'warn', 'warning'].includes(normalized)) {
|
|
537
|
-
return '33';
|
|
538
|
-
}
|
|
539
|
-
if (['error', 'fail', 'inactive', 'unsafe'].includes(normalized)) {
|
|
540
|
-
return '31';
|
|
541
|
-
}
|
|
542
|
-
return null;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function colorizeDoctorOutput(text, status) {
|
|
546
|
-
const colorCode = doctorOutputColorCode(status);
|
|
547
|
-
return colorCode ? colorize(text, colorCode) : text;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
210
|
/**
|
|
551
211
|
* @typedef {Object} AutoFinishSummary
|
|
552
212
|
* @property {boolean} [enabled]
|
|
@@ -601,658 +261,6 @@ function colorizeDoctorOutput(text, status) {
|
|
|
601
261
|
* @property {AutoFinishSummary} autoFinish
|
|
602
262
|
* @property {string | null} sandboxLockContent
|
|
603
263
|
*/
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* @param {string | null | undefined} detail
|
|
607
|
-
* @returns {string | null}
|
|
608
|
-
*/
|
|
609
|
-
function detectAutoFinishDetailStatus(detail) {
|
|
610
|
-
const trimmed = String(detail || '').trim();
|
|
611
|
-
const match = trimmed.match(/^\[(\w+)\]/);
|
|
612
|
-
if (match) {
|
|
613
|
-
return match[1].toLowerCase();
|
|
614
|
-
}
|
|
615
|
-
if (/^Skipped\b/i.test(trimmed) || /^No local agent branches found\b/i.test(trimmed)) {
|
|
616
|
-
return 'skip';
|
|
617
|
-
}
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* @param {AutoFinishSummary | null | undefined} summary
|
|
623
|
-
* @returns {string | null}
|
|
624
|
-
*/
|
|
625
|
-
function detectAutoFinishSummaryStatus(summary) {
|
|
626
|
-
if (!summary || summary.enabled === false) {
|
|
627
|
-
return detectAutoFinishDetailStatus(summary?.details?.[0]);
|
|
628
|
-
}
|
|
629
|
-
if ((summary.failed || 0) > 0) {
|
|
630
|
-
return 'fail';
|
|
631
|
-
}
|
|
632
|
-
if ((summary.completed || 0) > 0) {
|
|
633
|
-
return 'done';
|
|
634
|
-
}
|
|
635
|
-
if ((summary.skipped || 0) > 0) {
|
|
636
|
-
return 'skip';
|
|
637
|
-
}
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function statusDot(status) {
|
|
642
|
-
if (status === 'active') {
|
|
643
|
-
return colorize('●', '32'); // green
|
|
644
|
-
}
|
|
645
|
-
if (status === 'inactive') {
|
|
646
|
-
return colorize('●', '31'); // red
|
|
647
|
-
}
|
|
648
|
-
if (status === 'disabled') {
|
|
649
|
-
return colorize('●', '36'); // cyan
|
|
650
|
-
}
|
|
651
|
-
return colorize('●', '33'); // yellow for degraded/unknown
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function commandCatalogLines(indent = ' ') {
|
|
655
|
-
const maxCommandLength = CLI_COMMAND_DESCRIPTIONS.reduce(
|
|
656
|
-
(max, [command]) => Math.max(max, command.length),
|
|
657
|
-
0,
|
|
658
|
-
);
|
|
659
|
-
return CLI_COMMAND_DESCRIPTIONS.map(
|
|
660
|
-
([command, description]) => `${indent}${command.padEnd(maxCommandLength + 2)}${description}`,
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
function agentBotCatalogLines(indent = ' ') {
|
|
665
|
-
const maxCommandLength = AGENT_BOT_DESCRIPTIONS.reduce(
|
|
666
|
-
(max, [command]) => Math.max(max, command.length),
|
|
667
|
-
0,
|
|
668
|
-
);
|
|
669
|
-
return AGENT_BOT_DESCRIPTIONS.map(
|
|
670
|
-
([command, description]) => `${indent}${command.padEnd(maxCommandLength + 2)}${description}`,
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
function repoToggleLines(indent = ' ') {
|
|
675
|
-
return [
|
|
676
|
-
`${indent}Set repo-root .env: ${GUARDEX_REPO_TOGGLE_ENV}=0 disables Guardex, ${GUARDEX_REPO_TOGGLE_ENV}=1 enables it again`,
|
|
677
|
-
];
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function printToolLogsSummary() {
|
|
681
|
-
const usageLine = ` $ ${SHORT_TOOL_NAME} <command> [options]`;
|
|
682
|
-
const commandDetails = commandCatalogLines(' ');
|
|
683
|
-
const agentBotDetails = agentBotCatalogLines(' ');
|
|
684
|
-
const repoToggleDetails = repoToggleLines(' ');
|
|
685
|
-
|
|
686
|
-
if (!supportsAnsiColors()) {
|
|
687
|
-
console.log(`${TOOL_NAME}-tools logs:`);
|
|
688
|
-
console.log(' USAGE');
|
|
689
|
-
console.log(usageLine);
|
|
690
|
-
console.log(' COMMANDS');
|
|
691
|
-
for (const line of commandDetails) {
|
|
692
|
-
console.log(line);
|
|
693
|
-
}
|
|
694
|
-
console.log(' AGENT BOT');
|
|
695
|
-
for (const line of agentBotDetails) {
|
|
696
|
-
console.log(line);
|
|
697
|
-
}
|
|
698
|
-
console.log(' REPO TOGGLE');
|
|
699
|
-
for (const line of repoToggleDetails) {
|
|
700
|
-
console.log(line);
|
|
701
|
-
}
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const title = colorize(`${TOOL_NAME}-tools logs`, '1;36');
|
|
706
|
-
const usageHeader = colorize('USAGE', '1');
|
|
707
|
-
const commandsHeader = colorize('COMMANDS', '1');
|
|
708
|
-
const agentBotHeader = colorize('AGENT BOT', '1');
|
|
709
|
-
const repoToggleHeader = colorize('REPO TOGGLE', '1');
|
|
710
|
-
const pipe = colorize('│', '90');
|
|
711
|
-
const tee = colorize('├', '90');
|
|
712
|
-
const corner = colorize('└', '90');
|
|
713
|
-
|
|
714
|
-
console.log(`${title}:`);
|
|
715
|
-
console.log(` ${tee}─ ${usageHeader}`);
|
|
716
|
-
console.log(` ${pipe}${usageLine}`);
|
|
717
|
-
console.log(` ${tee}─ ${commandsHeader}`);
|
|
718
|
-
for (const line of commandDetails) {
|
|
719
|
-
if (!line) {
|
|
720
|
-
console.log(` ${pipe}`);
|
|
721
|
-
continue;
|
|
722
|
-
}
|
|
723
|
-
console.log(` ${pipe}${line.slice(2)}`);
|
|
724
|
-
}
|
|
725
|
-
console.log(` ${tee}─ ${agentBotHeader}`);
|
|
726
|
-
for (const line of agentBotDetails) {
|
|
727
|
-
if (!line) {
|
|
728
|
-
console.log(` ${pipe}`);
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
console.log(` ${pipe}${line.slice(2)}`);
|
|
732
|
-
}
|
|
733
|
-
console.log(` ${tee}─ ${repoToggleHeader}`);
|
|
734
|
-
for (const line of repoToggleDetails) {
|
|
735
|
-
if (!line) {
|
|
736
|
-
console.log(` ${pipe}`);
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
console.log(` ${pipe}${line.slice(2)}`);
|
|
740
|
-
}
|
|
741
|
-
console.log(` ${corner}─ ${colorize(`Try '${TOOL_NAME} doctor' for one-step repair + verification.`, '2')}`);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
function usage(options = {}) {
|
|
745
|
-
const { outsideGitRepo = false } = options;
|
|
746
|
-
|
|
747
|
-
console.log(`A command-line tool that sets up hardened multi-agent safety for git repositories.
|
|
748
|
-
|
|
749
|
-
VERSION
|
|
750
|
-
${runtimeVersion()}
|
|
751
|
-
|
|
752
|
-
USAGE
|
|
753
|
-
$ ${SHORT_TOOL_NAME} <command> [options]
|
|
754
|
-
|
|
755
|
-
COMMANDS
|
|
756
|
-
${commandCatalogLines().join('\n')}
|
|
757
|
-
|
|
758
|
-
AGENT BOT
|
|
759
|
-
${agentBotCatalogLines().join('\n')}
|
|
760
|
-
|
|
761
|
-
REPO TOGGLE
|
|
762
|
-
${repoToggleLines().join('\n')}
|
|
763
|
-
|
|
764
|
-
NOTES
|
|
765
|
-
- No command = ${SHORT_TOOL_NAME} status. ${SHORT_TOOL_NAME} init is an alias of ${SHORT_TOOL_NAME} setup.
|
|
766
|
-
- Global installs need Y/N approval; GitHub CLI (gh) is required for PR automation.
|
|
767
|
-
- Target another repo: ${SHORT_TOOL_NAME} <cmd> --target <repo-path>.
|
|
768
|
-
- On protected main, setup/install/fix/doctor auto-sandbox via agent branch + PR flow.
|
|
769
|
-
- Run '${SHORT_TOOL_NAME} cleanup' to prune merged agent branches/worktrees.
|
|
770
|
-
- Legacy aliases: ${LEGACY_NAMES.join(', ')}.`);
|
|
771
|
-
|
|
772
|
-
if (outsideGitRepo) {
|
|
773
|
-
console.log(`
|
|
774
|
-
[${TOOL_NAME}] No git repository detected in current directory.
|
|
775
|
-
[${TOOL_NAME}] Start from a repo root, or pass an explicit target:
|
|
776
|
-
${TOOL_NAME} setup --target <path-to-git-repo>
|
|
777
|
-
${TOOL_NAME} doctor --target <path-to-git-repo>`);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
function run(cmd, args, options = {}) {
|
|
782
|
-
return cp.spawnSync(cmd, args, {
|
|
783
|
-
encoding: 'utf8',
|
|
784
|
-
stdio: options.stdio || 'pipe',
|
|
785
|
-
cwd: options.cwd,
|
|
786
|
-
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
787
|
-
timeout: options.timeout,
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
function extractTargetedArgs(rawArgs, defaultTarget = process.cwd()) {
|
|
792
|
-
const passthrough = [];
|
|
793
|
-
let target = defaultTarget;
|
|
794
|
-
|
|
795
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
796
|
-
const arg = rawArgs[index];
|
|
797
|
-
if (arg === '--target' || arg === '-t') {
|
|
798
|
-
target = requireValue(rawArgs, index, '--target');
|
|
799
|
-
index += 1;
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
passthrough.push(arg);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
return { target, passthrough };
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function packageAssetEnv(extraEnv = {}) {
|
|
809
|
-
return {
|
|
810
|
-
GUARDEX_CLI_ENTRY: __filename,
|
|
811
|
-
GUARDEX_NODE_BIN: process.execPath,
|
|
812
|
-
...extraEnv,
|
|
813
|
-
};
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
function packageAssetPath(assetKey) {
|
|
817
|
-
const assetPath = PACKAGE_SCRIPT_ASSETS[assetKey];
|
|
818
|
-
if (!assetPath) {
|
|
819
|
-
throw new Error(`Unknown package asset: ${assetKey}`);
|
|
820
|
-
}
|
|
821
|
-
if (!fs.existsSync(assetPath)) {
|
|
822
|
-
throw new Error(`Missing package asset: ${assetPath}`);
|
|
823
|
-
}
|
|
824
|
-
return assetPath;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
function runPackageAsset(assetKey, rawArgs, options = {}) {
|
|
828
|
-
const assetPath = packageAssetPath(assetKey);
|
|
829
|
-
let cmd = 'bash';
|
|
830
|
-
if (assetPath.endsWith('.py')) {
|
|
831
|
-
cmd = 'python3';
|
|
832
|
-
} else if (assetPath.endsWith('.js')) {
|
|
833
|
-
cmd = process.execPath;
|
|
834
|
-
}
|
|
835
|
-
return run(cmd, [assetPath, ...rawArgs], {
|
|
836
|
-
cwd: options.cwd || process.cwd(),
|
|
837
|
-
stdio: options.stdio || 'pipe',
|
|
838
|
-
timeout: options.timeout,
|
|
839
|
-
env: packageAssetEnv(options.env),
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
function repoLocalLegacyScriptPath(repoRoot, relativePath) {
|
|
844
|
-
const assetPath = path.join(repoRoot, relativePath);
|
|
845
|
-
return fs.existsSync(assetPath) ? assetPath : null;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
function runReviewBotCommand(repoRoot, rawArgs, options = {}) {
|
|
849
|
-
const legacyScript = repoLocalLegacyScriptPath(repoRoot, 'scripts/review-bot-watch.sh');
|
|
850
|
-
if (legacyScript) {
|
|
851
|
-
return run('bash', [legacyScript, ...rawArgs], {
|
|
852
|
-
cwd: repoRoot,
|
|
853
|
-
stdio: options.stdio || 'pipe',
|
|
854
|
-
timeout: options.timeout,
|
|
855
|
-
env: packageAssetEnv(options.env),
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
return runPackageAsset('reviewBot', rawArgs, {
|
|
859
|
-
...options,
|
|
860
|
-
cwd: repoRoot,
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
function invokePackageAsset(assetKey, rawArgs, options = {}) {
|
|
865
|
-
const result = runPackageAsset(assetKey, rawArgs, options);
|
|
866
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
867
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
868
|
-
if (result.status !== 0) {
|
|
869
|
-
throw new Error(`${assetKey} command failed with status ${result.status}`);
|
|
870
|
-
}
|
|
871
|
-
process.exitCode = 0;
|
|
872
|
-
return result;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
function formatElapsedDuration(ms) {
|
|
876
|
-
const durationMs = Number.isFinite(ms) ? Math.max(0, ms) : 0;
|
|
877
|
-
if (durationMs < 1000) {
|
|
878
|
-
return `${Math.round(durationMs)}ms`;
|
|
879
|
-
}
|
|
880
|
-
if (durationMs < 10_000) {
|
|
881
|
-
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
882
|
-
}
|
|
883
|
-
return `${Math.round(durationMs / 1000)}s`;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
function truncateMiddle(value, maxLength) {
|
|
887
|
-
const text = String(value || '');
|
|
888
|
-
const limit = Number.isFinite(maxLength) ? Math.max(4, maxLength) : 0;
|
|
889
|
-
if (!limit || text.length <= limit) {
|
|
890
|
-
return text;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
const visible = limit - 1;
|
|
894
|
-
const headLength = Math.ceil(visible / 2);
|
|
895
|
-
const tailLength = Math.floor(visible / 2);
|
|
896
|
-
return `${text.slice(0, headLength)}…${text.slice(text.length - tailLength)}`;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function truncateTail(value, maxLength) {
|
|
900
|
-
const text = String(value || '');
|
|
901
|
-
const limit = Number.isFinite(maxLength) ? Math.max(4, maxLength) : 0;
|
|
902
|
-
if (!limit || text.length <= limit) {
|
|
903
|
-
return text;
|
|
904
|
-
}
|
|
905
|
-
return `${text.slice(0, limit - 1)}…`;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
function compactAutoFinishPathSegments(message) {
|
|
909
|
-
return String(message || '').replace(/\((\/[^)]+)\)/g, (_, rawPath) => {
|
|
910
|
-
if (
|
|
911
|
-
rawPath.includes(`${path.sep}.omx${path.sep}agent-worktrees${path.sep}`) ||
|
|
912
|
-
rawPath.includes(`${path.sep}.omc${path.sep}agent-worktrees${path.sep}`)
|
|
913
|
-
) {
|
|
914
|
-
return `(${path.basename(rawPath)})`;
|
|
915
|
-
}
|
|
916
|
-
return `(${truncateMiddle(rawPath, 72)})`;
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
function detectRecoverableAutoFinishConflict(message) {
|
|
921
|
-
const text = String(message || '').trim();
|
|
922
|
-
if (!text) {
|
|
923
|
-
return null;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (/rebase --continue/i.test(text) && /rebase --abort/i.test(text)) {
|
|
927
|
-
return {
|
|
928
|
-
rawLabel: 'auto-finish requires manual rebase.',
|
|
929
|
-
summary: 'manual rebase required in the source-probe worktree; run rebase --continue or rebase --abort',
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
if (/Rebase\/merge '.+' into '.+' and resolve conflicts before finishing\./i.test(text)) {
|
|
934
|
-
return {
|
|
935
|
-
rawLabel: 'auto-finish requires manual rebase or merge.',
|
|
936
|
-
summary: 'manual rebase or merge required before auto-finish can continue',
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
if (/Merge conflict detected while merging/i.test(text)) {
|
|
941
|
-
return {
|
|
942
|
-
rawLabel: 'auto-finish requires manual merge resolution.',
|
|
943
|
-
summary: 'manual merge resolution required before auto-finish can continue',
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
return null;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
function summarizeAutoFinishDetail(detail) {
|
|
951
|
-
const trimmed = String(detail || '').trim();
|
|
952
|
-
const match = trimmed.match(/^\[(\w+)\]\s+([^:]+):\s*(.*)$/);
|
|
953
|
-
if (!match) {
|
|
954
|
-
return truncateTail(compactAutoFinishPathSegments(trimmed), DOCTOR_AUTO_FINISH_MESSAGE_MAX);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
const [, status, rawBranch, rawMessage] = match;
|
|
958
|
-
const branch = truncateMiddle(rawBranch, DOCTOR_AUTO_FINISH_BRANCH_LABEL_MAX);
|
|
959
|
-
let message = String(rawMessage || '').trim();
|
|
960
|
-
const recoverableConflict = status === 'skip' ? detectRecoverableAutoFinishConflict(message) : null;
|
|
961
|
-
|
|
962
|
-
if (recoverableConflict) {
|
|
963
|
-
message = recoverableConflict.summary;
|
|
964
|
-
} else if (status === 'fail') {
|
|
965
|
-
message = message.replace(/^auto-finish failed\.?\s*/i, '');
|
|
966
|
-
if (/\[agent-sync-guard\]/.test(message) && /Resolve conflicts/i.test(message)) {
|
|
967
|
-
message = 'rebase conflict in finish flow; run rebase --continue or rebase --abort in the source-probe worktree';
|
|
968
|
-
} else if (/unable to compute ahead\/behind/i.test(message)) {
|
|
969
|
-
const aheadBehindMatch = message.match(/unable to compute ahead\/behind(?: \([^)]+\))?/i);
|
|
970
|
-
if (aheadBehindMatch) {
|
|
971
|
-
message = aheadBehindMatch[0];
|
|
972
|
-
}
|
|
973
|
-
} else if (/remote ref does not exist/i.test(message)) {
|
|
974
|
-
message = 'branch merged, but the remote ref was already removed during cleanup';
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
message = compactAutoFinishPathSegments(message)
|
|
979
|
-
.replace(/\s+\|\s+/g, '; ')
|
|
980
|
-
.trim();
|
|
981
|
-
|
|
982
|
-
return `[${status}] ${branch}: ${truncateTail(message, DOCTOR_AUTO_FINISH_MESSAGE_MAX)}`;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* @param {AutoFinishSummary | null | undefined} summary
|
|
987
|
-
* @param {{ baseBranch?: string, verbose?: boolean, detailLimit?: number }} [options]
|
|
988
|
-
*/
|
|
989
|
-
function printAutoFinishSummary(summary, options = {}) {
|
|
990
|
-
const enabled = Boolean(summary && summary.enabled);
|
|
991
|
-
const details = Array.isArray(summary && summary.details) ? summary.details : [];
|
|
992
|
-
const baseBranch = String(options.baseBranch || summary?.baseBranch || '').trim();
|
|
993
|
-
const verbose = Boolean(options.verbose);
|
|
994
|
-
const detailLimit = Number.isFinite(options.detailLimit)
|
|
995
|
-
? Math.max(0, options.detailLimit)
|
|
996
|
-
: DOCTOR_AUTO_FINISH_DETAIL_LIMIT;
|
|
997
|
-
|
|
998
|
-
if (enabled) {
|
|
999
|
-
console.log(
|
|
1000
|
-
colorizeDoctorOutput(
|
|
1001
|
-
`[${TOOL_NAME}] Auto-finish sweep (base=${baseBranch}): attempted=${summary.attempted}, completed=${summary.completed}, skipped=${summary.skipped}, failed=${summary.failed}`,
|
|
1002
|
-
detectAutoFinishSummaryStatus(summary),
|
|
1003
|
-
),
|
|
1004
|
-
);
|
|
1005
|
-
const visibleDetails = verbose ? details : details.slice(0, detailLimit).map(summarizeAutoFinishDetail);
|
|
1006
|
-
for (const detail of visibleDetails) {
|
|
1007
|
-
console.log(colorizeDoctorOutput(`[${TOOL_NAME}] ${detail}`, detectAutoFinishDetailStatus(detail)));
|
|
1008
|
-
}
|
|
1009
|
-
if (!verbose && details.length > detailLimit) {
|
|
1010
|
-
console.log(
|
|
1011
|
-
colorizeDoctorOutput(
|
|
1012
|
-
`[${TOOL_NAME}] … ${details.length - detailLimit} more branch result(s). Re-run with --verbose-auto-finish for full details.`,
|
|
1013
|
-
'warn',
|
|
1014
|
-
),
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
if (details.length > 0) {
|
|
1021
|
-
const detail = verbose ? details[0] : summarizeAutoFinishDetail(details[0]);
|
|
1022
|
-
console.log(colorizeDoctorOutput(`[${TOOL_NAME}] ${detail}`, detectAutoFinishDetailStatus(detail)));
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
function gitRun(repoRoot, args, { allowFailure = false } = {}) {
|
|
1027
|
-
const result = run('git', ['-C', repoRoot, ...args]);
|
|
1028
|
-
if (!allowFailure && result.status !== 0) {
|
|
1029
|
-
throw new Error(`git ${args.join(' ')} failed: ${(result.stderr || '').trim()}`);
|
|
1030
|
-
}
|
|
1031
|
-
return result;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
function resolveRepoRoot(targetPath) {
|
|
1035
|
-
const resolvedTarget = path.resolve(targetPath || process.cwd());
|
|
1036
|
-
const result = run('git', ['-C', resolvedTarget, 'rev-parse', '--show-toplevel']);
|
|
1037
|
-
if (result.status !== 0) {
|
|
1038
|
-
const stderr = (result.stderr || '').trim();
|
|
1039
|
-
throw new Error(
|
|
1040
|
-
`Target is not inside a git repository: ${resolvedTarget}${stderr ? `\n${stderr}` : ''}`,
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
return result.stdout.trim();
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
function isGitRepo(targetPath) {
|
|
1047
|
-
const resolvedTarget = path.resolve(targetPath || process.cwd());
|
|
1048
|
-
const result = run('git', ['-C', resolvedTarget, 'rev-parse', '--show-toplevel']);
|
|
1049
|
-
return result.status === 0;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const NESTED_REPO_DEFAULT_MAX_DEPTH = 6;
|
|
1053
|
-
const NESTED_REPO_DEFAULT_SKIP_DIRS = new Set([
|
|
1054
|
-
'node_modules',
|
|
1055
|
-
'.git',
|
|
1056
|
-
'dist',
|
|
1057
|
-
'build',
|
|
1058
|
-
'.next',
|
|
1059
|
-
'.cache',
|
|
1060
|
-
'target',
|
|
1061
|
-
'vendor',
|
|
1062
|
-
'.venv',
|
|
1063
|
-
'.pnpm-store',
|
|
1064
|
-
]);
|
|
1065
|
-
function discoverNestedGitRepos(rootPath, opts = {}) {
|
|
1066
|
-
const maxDepth = Number.isFinite(opts.maxDepth) ? Math.max(1, opts.maxDepth) : NESTED_REPO_DEFAULT_MAX_DEPTH;
|
|
1067
|
-
const extraSkip = new Set(Array.isArray(opts.extraSkip) ? opts.extraSkip : []);
|
|
1068
|
-
const includeSubmodules = Boolean(opts.includeSubmodules);
|
|
1069
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
1070
|
-
|
|
1071
|
-
const rootCommonDir = (() => {
|
|
1072
|
-
const result = run('git', ['-C', resolvedRoot, 'rev-parse', '--git-common-dir'], { cwd: resolvedRoot });
|
|
1073
|
-
if (result.status !== 0) return null;
|
|
1074
|
-
const raw = result.stdout.trim();
|
|
1075
|
-
if (!raw) return null;
|
|
1076
|
-
return path.resolve(resolvedRoot, raw);
|
|
1077
|
-
})();
|
|
1078
|
-
|
|
1079
|
-
const worktreeSkipAbsolutes = AGENT_WORKTREE_RELATIVE_DIRS.map((relativeDir) => path.join(resolvedRoot, relativeDir));
|
|
1080
|
-
const found = new Set();
|
|
1081
|
-
found.add(resolvedRoot);
|
|
1082
|
-
|
|
1083
|
-
function shouldSkipDir(dirName) {
|
|
1084
|
-
return NESTED_REPO_DEFAULT_SKIP_DIRS.has(dirName) || extraSkip.has(dirName);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
function walk(currentPath, depth) {
|
|
1088
|
-
if (depth > maxDepth) return;
|
|
1089
|
-
let entries;
|
|
1090
|
-
try {
|
|
1091
|
-
entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
1092
|
-
} catch {
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
for (const entry of entries) {
|
|
1097
|
-
const entryPath = path.join(currentPath, entry.name);
|
|
1098
|
-
|
|
1099
|
-
if (entry.name === '.git') {
|
|
1100
|
-
if (entry.isDirectory()) {
|
|
1101
|
-
if (entryPath === path.join(resolvedRoot, '.git')) continue;
|
|
1102
|
-
found.add(path.dirname(entryPath));
|
|
1103
|
-
} else if (includeSubmodules && entry.isFile()) {
|
|
1104
|
-
found.add(path.dirname(entryPath));
|
|
1105
|
-
}
|
|
1106
|
-
continue;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
|
|
1110
|
-
if (shouldSkipDir(entry.name)) continue;
|
|
1111
|
-
if (worktreeSkipAbsolutes.includes(entryPath)) continue;
|
|
1112
|
-
walk(entryPath, depth + 1);
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
walk(resolvedRoot, 0);
|
|
1117
|
-
|
|
1118
|
-
const filtered = Array.from(found).filter((repoPath) => {
|
|
1119
|
-
if (repoPath === resolvedRoot) return true;
|
|
1120
|
-
if (!rootCommonDir) return true;
|
|
1121
|
-
const childResult = run('git', ['-C', repoPath, 'rev-parse', '--git-common-dir'], { cwd: repoPath });
|
|
1122
|
-
if (childResult.status !== 0) return true;
|
|
1123
|
-
const childCommonDirRaw = childResult.stdout.trim();
|
|
1124
|
-
if (!childCommonDirRaw) return true;
|
|
1125
|
-
const childCommonDir = path.resolve(repoPath, childCommonDirRaw);
|
|
1126
|
-
return childCommonDir !== rootCommonDir;
|
|
1127
|
-
});
|
|
1128
|
-
|
|
1129
|
-
const [root, ...rest] = filtered;
|
|
1130
|
-
rest.sort((a, b) => a.localeCompare(b));
|
|
1131
|
-
return [root, ...rest];
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
function toDestinationPath(relativeTemplatePath) {
|
|
1135
|
-
if (relativeTemplatePath.startsWith('scripts/')) {
|
|
1136
|
-
return relativeTemplatePath;
|
|
1137
|
-
}
|
|
1138
|
-
if (relativeTemplatePath.startsWith('githooks/')) {
|
|
1139
|
-
return `.${relativeTemplatePath}`;
|
|
1140
|
-
}
|
|
1141
|
-
if (relativeTemplatePath.startsWith('codex/')) {
|
|
1142
|
-
return `.${relativeTemplatePath}`;
|
|
1143
|
-
}
|
|
1144
|
-
if (relativeTemplatePath.startsWith('claude/')) {
|
|
1145
|
-
return `.${relativeTemplatePath}`;
|
|
1146
|
-
}
|
|
1147
|
-
if (relativeTemplatePath.startsWith('github/')) {
|
|
1148
|
-
return `.${relativeTemplatePath}`;
|
|
1149
|
-
}
|
|
1150
|
-
if (relativeTemplatePath.startsWith('vscode/')) {
|
|
1151
|
-
return relativeTemplatePath;
|
|
1152
|
-
}
|
|
1153
|
-
throw new Error(`Unsupported template path: ${relativeTemplatePath}`);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
function ensureParentDir(repoRoot, filePath, dryRun) {
|
|
1157
|
-
if (dryRun) return;
|
|
1158
|
-
|
|
1159
|
-
const parentDir = path.dirname(filePath);
|
|
1160
|
-
const relativeParentDir = path.relative(repoRoot, parentDir);
|
|
1161
|
-
const segments = relativeParentDir.split(path.sep).filter(Boolean);
|
|
1162
|
-
let currentPath = repoRoot;
|
|
1163
|
-
|
|
1164
|
-
for (const segment of segments) {
|
|
1165
|
-
currentPath = path.join(currentPath, segment);
|
|
1166
|
-
if (fs.existsSync(currentPath) && !fs.statSync(currentPath).isDirectory()) {
|
|
1167
|
-
const blockingPath = path.relative(repoRoot, currentPath) || path.basename(currentPath);
|
|
1168
|
-
const targetPath = path.relative(repoRoot, filePath) || path.basename(filePath);
|
|
1169
|
-
throw new Error(
|
|
1170
|
-
`Path conflict: ${blockingPath} exists as a file, but ${targetPath} needs it to be a directory. ` +
|
|
1171
|
-
`Remove or rename ${blockingPath} and rerun '${SHORT_TOOL_NAME} setup'.`,
|
|
1172
|
-
);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
fs.mkdirSync(parentDir, { recursive: true });
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
function ensureExecutable(destinationPath, relativePath, dryRun) {
|
|
1180
|
-
if (dryRun) return;
|
|
1181
|
-
if (EXECUTABLE_RELATIVE_PATHS.has(relativePath)) {
|
|
1182
|
-
fs.chmodSync(destinationPath, 0o755);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
function isCriticalGuardrailPath(relativePath) {
|
|
1187
|
-
return CRITICAL_GUARDRAIL_PATHS.has(relativePath);
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
function shellSingleQuote(value) {
|
|
1191
|
-
return `'${String(value).replace(/'/g, `'\"'\"'`)}'`;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
function renderShellDispatchShim(commandParts) {
|
|
1195
|
-
const rendered = commandParts.map((part) => shellSingleQuote(part)).join(' ');
|
|
1196
|
-
return (
|
|
1197
|
-
'#!/usr/bin/env bash\n' +
|
|
1198
|
-
'set -euo pipefail\n' +
|
|
1199
|
-
'\n' +
|
|
1200
|
-
'if [[ -n "${GUARDEX_CLI_ENTRY:-}" ]]; then\n' +
|
|
1201
|
-
' node_bin="${GUARDEX_NODE_BIN:-node}"\n' +
|
|
1202
|
-
` exec "$node_bin" "$GUARDEX_CLI_ENTRY" ${rendered} "$@"\n` +
|
|
1203
|
-
'fi\n' +
|
|
1204
|
-
'\n' +
|
|
1205
|
-
'resolve_guardex_cli() {\n' +
|
|
1206
|
-
' if [[ -n "${GUARDEX_CLI_BIN:-}" ]]; then\n' +
|
|
1207
|
-
' printf \'%s\' "$GUARDEX_CLI_BIN"\n' +
|
|
1208
|
-
' return 0\n' +
|
|
1209
|
-
' fi\n' +
|
|
1210
|
-
' if command -v gx >/dev/null 2>&1; then\n' +
|
|
1211
|
-
' printf \'%s\' "gx"\n' +
|
|
1212
|
-
' return 0\n' +
|
|
1213
|
-
' fi\n' +
|
|
1214
|
-
' if command -v gitguardex >/dev/null 2>&1; then\n' +
|
|
1215
|
-
' printf \'%s\' "gitguardex"\n' +
|
|
1216
|
-
' return 0\n' +
|
|
1217
|
-
' fi\n' +
|
|
1218
|
-
' echo "[gitguardex-shim] Missing gx CLI in PATH." >&2\n' +
|
|
1219
|
-
' exit 1\n' +
|
|
1220
|
-
'}\n' +
|
|
1221
|
-
'\n' +
|
|
1222
|
-
'cli_bin="$(resolve_guardex_cli)"\n' +
|
|
1223
|
-
`exec "$cli_bin" ${rendered} "$@"\n`
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
function renderPythonDispatchShim(commandParts) {
|
|
1228
|
-
return (
|
|
1229
|
-
'#!/usr/bin/env python3\n' +
|
|
1230
|
-
'import os\n' +
|
|
1231
|
-
'import shutil\n' +
|
|
1232
|
-
'import subprocess\n' +
|
|
1233
|
-
'import sys\n' +
|
|
1234
|
-
'\n' +
|
|
1235
|
-
`COMMAND = ${JSON.stringify(commandParts)}\n` +
|
|
1236
|
-
'\n' +
|
|
1237
|
-
'entry = os.environ.get("GUARDEX_CLI_ENTRY")\n' +
|
|
1238
|
-
'if entry:\n' +
|
|
1239
|
-
' node_bin = os.environ.get("GUARDEX_NODE_BIN") or shutil.which("node") or "node"\n' +
|
|
1240
|
-
' raise SystemExit(subprocess.call([node_bin, entry, *COMMAND, *sys.argv[1:]]))\n' +
|
|
1241
|
-
'cli = os.environ.get("GUARDEX_CLI_BIN") or shutil.which("gx") or shutil.which("gitguardex")\n' +
|
|
1242
|
-
'if not cli:\n' +
|
|
1243
|
-
' sys.stderr.write("[gitguardex-shim] Missing gx CLI in PATH.\\n")\n' +
|
|
1244
|
-
' raise SystemExit(1)\n' +
|
|
1245
|
-
'raise SystemExit(subprocess.call([cli, *COMMAND, *sys.argv[1:]]))\n'
|
|
1246
|
-
);
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
function managedForceConflictMessage(relativePath) {
|
|
1250
|
-
return (
|
|
1251
|
-
`Refusing to overwrite existing file without --force: ${relativePath}\n` +
|
|
1252
|
-
`Use '--force ${relativePath}' to rewrite only this managed file, or '--force' to rewrite all managed files.`
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
264
|
function renderManagedFile(repoRoot, relativePath, content, options = {}) {
|
|
1257
265
|
const destinationPath = path.join(repoRoot, relativePath);
|
|
1258
266
|
const destinationExists = fs.existsSync(destinationPath);
|
|
@@ -1854,252 +862,29 @@ function configureHooks(repoRoot, dryRun) {
|
|
|
1854
862
|
throw new Error(`Failed to set git hooksPath: ${(result.stderr || '').trim()}`);
|
|
1855
863
|
}
|
|
1856
864
|
|
|
1857
|
-
return { status: 'set', key: 'core.hooksPath', value: '.githooks' };
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
function requireValue(rawArgs, index, flagName) {
|
|
1861
|
-
const value = rawArgs[index + 1];
|
|
1862
|
-
if (!value || value.startsWith('-')) {
|
|
1863
|
-
throw new Error(`${flagName} requires a value`);
|
|
1864
|
-
}
|
|
1865
|
-
return value;
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
function normalizeManagedForcePath(rawPath) {
|
|
1869
|
-
if (typeof rawPath !== 'string') {
|
|
1870
|
-
return null;
|
|
1871
|
-
}
|
|
1872
|
-
const normalized = path.posix.normalize(rawPath.replace(/\\/g, '/'));
|
|
1873
|
-
if (!normalized || normalized === '.' || normalized.startsWith('../') || path.posix.isAbsolute(normalized)) {
|
|
1874
|
-
return null;
|
|
1875
|
-
}
|
|
1876
|
-
return normalized.startsWith('./') ? normalized.slice(2) : normalized;
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
function collectForceManagedPaths(rawArgs, startIndex) {
|
|
1880
|
-
const forceManagedPaths = [];
|
|
1881
|
-
let nextIndex = startIndex;
|
|
1882
|
-
|
|
1883
|
-
while (nextIndex + 1 < rawArgs.length) {
|
|
1884
|
-
const candidate = rawArgs[nextIndex + 1];
|
|
1885
|
-
if (!candidate || candidate.startsWith('-')) {
|
|
1886
|
-
break;
|
|
1887
|
-
}
|
|
1888
|
-
const normalized = normalizeManagedForcePath(candidate);
|
|
1889
|
-
if (!normalized || !TARGETED_FORCEABLE_MANAGED_PATHS.has(normalized)) {
|
|
1890
|
-
throw new Error(`Unknown managed path after --force: ${candidate}`);
|
|
1891
|
-
}
|
|
1892
|
-
forceManagedPaths.push(normalized);
|
|
1893
|
-
nextIndex += 1;
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
return { forceManagedPaths, nextIndex };
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
function appendForceArgs(args, options) {
|
|
1900
|
-
if (!options.force) {
|
|
1901
|
-
return;
|
|
1902
|
-
}
|
|
1903
|
-
args.push('--force');
|
|
1904
|
-
for (const managedPath of options.forceManagedPaths || []) {
|
|
1905
|
-
args.push(managedPath);
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
function shouldForceManagedPath(options, relativePath) {
|
|
1910
|
-
if (!options.force) {
|
|
1911
|
-
return false;
|
|
1912
|
-
}
|
|
1913
|
-
const targetedPaths = Array.isArray(options.forceManagedPaths) ? options.forceManagedPaths : [];
|
|
1914
|
-
if (targetedPaths.length === 0) {
|
|
1915
|
-
return true;
|
|
1916
|
-
}
|
|
1917
|
-
const normalized = normalizeManagedForcePath(relativePath);
|
|
1918
|
-
return normalized !== null && targetedPaths.includes(normalized);
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
function parseCommonArgs(rawArgs, defaults) {
|
|
1922
|
-
const options = { ...defaults };
|
|
1923
|
-
const supportsForce = Object.prototype.hasOwnProperty.call(options, 'force');
|
|
1924
|
-
if (supportsForce && !Array.isArray(options.forceManagedPaths)) {
|
|
1925
|
-
options.forceManagedPaths = [];
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
1929
|
-
const arg = rawArgs[index];
|
|
1930
|
-
if (arg === '--target' || arg === '-t') {
|
|
1931
|
-
options.target = requireValue(rawArgs, index, '--target');
|
|
1932
|
-
index += 1;
|
|
1933
|
-
continue;
|
|
1934
|
-
}
|
|
1935
|
-
if (arg === '--dry-run') {
|
|
1936
|
-
options.dryRun = true;
|
|
1937
|
-
continue;
|
|
1938
|
-
}
|
|
1939
|
-
if (arg === '--skip-agents') {
|
|
1940
|
-
options.skipAgents = true;
|
|
1941
|
-
continue;
|
|
1942
|
-
}
|
|
1943
|
-
if (arg === '--skip-package-json') {
|
|
1944
|
-
options.skipPackageJson = true;
|
|
1945
|
-
continue;
|
|
1946
|
-
}
|
|
1947
|
-
if (arg === '--force') {
|
|
1948
|
-
if (!supportsForce) {
|
|
1949
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
1950
|
-
}
|
|
1951
|
-
options.force = true;
|
|
1952
|
-
const parsed = collectForceManagedPaths(rawArgs, index);
|
|
1953
|
-
if (parsed.forceManagedPaths.length > 0) {
|
|
1954
|
-
options.forceManagedPaths = Array.from(
|
|
1955
|
-
new Set([...(options.forceManagedPaths || []), ...parsed.forceManagedPaths]),
|
|
1956
|
-
);
|
|
1957
|
-
}
|
|
1958
|
-
index = parsed.nextIndex;
|
|
1959
|
-
continue;
|
|
1960
|
-
}
|
|
1961
|
-
if (arg === '--keep-stale-locks') {
|
|
1962
|
-
options.dropStaleLocks = false;
|
|
1963
|
-
continue;
|
|
1964
|
-
}
|
|
1965
|
-
if (arg === '--json') {
|
|
1966
|
-
options.json = true;
|
|
1967
|
-
continue;
|
|
1968
|
-
}
|
|
1969
|
-
if (arg === '--yes-global-install') {
|
|
1970
|
-
options.yesGlobalInstall = true;
|
|
1971
|
-
continue;
|
|
1972
|
-
}
|
|
1973
|
-
if (arg === '--no-global-install') {
|
|
1974
|
-
options.noGlobalInstall = true;
|
|
1975
|
-
continue;
|
|
1976
|
-
}
|
|
1977
|
-
if (arg === '--no-gitignore') {
|
|
1978
|
-
options.skipGitignore = true;
|
|
1979
|
-
continue;
|
|
1980
|
-
}
|
|
1981
|
-
if (arg === '--allow-protected-base-write') {
|
|
1982
|
-
options.allowProtectedBaseWrite = true;
|
|
1983
|
-
continue;
|
|
1984
|
-
}
|
|
1985
|
-
if (Object.prototype.hasOwnProperty.call(options, 'waitForMerge') && arg === '--wait-for-merge') {
|
|
1986
|
-
options.waitForMerge = true;
|
|
1987
|
-
continue;
|
|
1988
|
-
}
|
|
1989
|
-
if (Object.prototype.hasOwnProperty.call(options, 'waitForMerge') && arg === '--no-wait-for-merge') {
|
|
1990
|
-
options.waitForMerge = false;
|
|
1991
|
-
continue;
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
|
-
if (!options.target) {
|
|
1998
|
-
throw new Error('--target requires a path value');
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
return options;
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
function parseRepoTraversalArgs(rawArgs, defaults) {
|
|
2005
|
-
const traversalDefaults = {
|
|
2006
|
-
...defaults,
|
|
2007
|
-
recursive: true,
|
|
2008
|
-
nestedMaxDepth: NESTED_REPO_DEFAULT_MAX_DEPTH,
|
|
2009
|
-
nestedSkipDirs: [],
|
|
2010
|
-
includeSubmodules: false,
|
|
2011
|
-
};
|
|
2012
|
-
const forwardedArgs = [];
|
|
2013
|
-
|
|
2014
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
2015
|
-
const arg = rawArgs[index];
|
|
2016
|
-
if (arg === '--no-recursive' || arg === '--no-nested' || arg === '--single-repo') {
|
|
2017
|
-
traversalDefaults.recursive = false;
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
if (arg === '--recursive' || arg === '--nested') {
|
|
2021
|
-
traversalDefaults.recursive = true;
|
|
2022
|
-
continue;
|
|
2023
|
-
}
|
|
2024
|
-
if (arg === '--max-depth') {
|
|
2025
|
-
const raw = requireValue(rawArgs, index, '--max-depth');
|
|
2026
|
-
const parsed = Number.parseInt(raw, 10);
|
|
2027
|
-
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
2028
|
-
throw new Error('--max-depth requires a positive integer');
|
|
2029
|
-
}
|
|
2030
|
-
traversalDefaults.nestedMaxDepth = parsed;
|
|
2031
|
-
index += 1;
|
|
2032
|
-
continue;
|
|
2033
|
-
}
|
|
2034
|
-
if (arg === '--skip-nested') {
|
|
2035
|
-
const raw = requireValue(rawArgs, index, '--skip-nested');
|
|
2036
|
-
traversalDefaults.nestedSkipDirs.push(raw);
|
|
2037
|
-
index += 1;
|
|
2038
|
-
continue;
|
|
2039
|
-
}
|
|
2040
|
-
if (arg === '--include-submodules') {
|
|
2041
|
-
traversalDefaults.includeSubmodules = true;
|
|
2042
|
-
continue;
|
|
2043
|
-
}
|
|
2044
|
-
forwardedArgs.push(arg);
|
|
2045
|
-
}
|
|
2046
|
-
|
|
2047
|
-
return parseCommonArgs(forwardedArgs, traversalDefaults);
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
function parseSetupArgs(rawArgs, defaults) {
|
|
2051
|
-
const setupDefaults = {
|
|
2052
|
-
...defaults,
|
|
2053
|
-
parentWorkspaceView: false,
|
|
2054
|
-
};
|
|
2055
|
-
const forwardedArgs = [];
|
|
2056
|
-
|
|
2057
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
2058
|
-
const arg = rawArgs[index];
|
|
2059
|
-
if (arg === '--parent-workspace-view') {
|
|
2060
|
-
setupDefaults.parentWorkspaceView = true;
|
|
2061
|
-
continue;
|
|
2062
|
-
}
|
|
2063
|
-
if (arg === '--no-parent-workspace-view') {
|
|
2064
|
-
setupDefaults.parentWorkspaceView = false;
|
|
2065
|
-
continue;
|
|
2066
|
-
}
|
|
2067
|
-
forwardedArgs.push(arg);
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
return parseRepoTraversalArgs(forwardedArgs, setupDefaults);
|
|
865
|
+
return { status: 'set', key: 'core.hooksPath', value: '.githooks' };
|
|
2071
866
|
}
|
|
2072
867
|
|
|
2073
|
-
function
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
skipGitignore: false,
|
|
2081
|
-
dryRun: false,
|
|
2082
|
-
json: false,
|
|
2083
|
-
allowProtectedBaseWrite: false,
|
|
2084
|
-
waitForMerge: true,
|
|
2085
|
-
verboseAutoFinish: false,
|
|
2086
|
-
};
|
|
2087
|
-
const forwardedArgs = [];
|
|
2088
|
-
|
|
2089
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
2090
|
-
const arg = rawArgs[index];
|
|
2091
|
-
if (arg === '--verbose-auto-finish') {
|
|
2092
|
-
doctorDefaults.verboseAutoFinish = true;
|
|
2093
|
-
continue;
|
|
2094
|
-
}
|
|
2095
|
-
if (arg === '--compact-auto-finish') {
|
|
2096
|
-
doctorDefaults.verboseAutoFinish = false;
|
|
2097
|
-
continue;
|
|
2098
|
-
}
|
|
2099
|
-
forwardedArgs.push(arg);
|
|
868
|
+
function appendForceArgs(args, options) {
|
|
869
|
+
if (!options.force) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
args.push('--force');
|
|
873
|
+
for (const managedPath of options.forceManagedPaths || []) {
|
|
874
|
+
args.push(managedPath);
|
|
2100
875
|
}
|
|
876
|
+
}
|
|
2101
877
|
|
|
2102
|
-
|
|
878
|
+
function shouldForceManagedPath(options, relativePath) {
|
|
879
|
+
if (!options.force) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
const targetedPaths = Array.isArray(options.forceManagedPaths) ? options.forceManagedPaths : [];
|
|
883
|
+
if (targetedPaths.length === 0) {
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
const normalized = normalizeManagedForcePath(relativePath);
|
|
887
|
+
return normalized !== null && targetedPaths.includes(normalized);
|
|
2103
888
|
}
|
|
2104
889
|
|
|
2105
890
|
function normalizeWorkspacePath(relativePath) {
|
|
@@ -2931,10 +1716,6 @@ function mergeDoctorSandboxRepairsBackToProtectedBase(options, blocked, metadata
|
|
|
2931
1716
|
};
|
|
2932
1717
|
}
|
|
2933
1718
|
|
|
2934
|
-
function syncDoctorLocalSupportFiles(repoRoot, dryRun) {
|
|
2935
|
-
return [];
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
1719
|
/**
|
|
2939
1720
|
* @param {string} [note]
|
|
2940
1721
|
* @returns {OperationResult}
|
|
@@ -3131,8 +1912,6 @@ function executeDoctorSandboxLifecycle(options, blocked, metadata) {
|
|
|
3131
1912
|
execution.finish,
|
|
3132
1913
|
);
|
|
3133
1914
|
|
|
3134
|
-
syncDoctorLocalSupportFiles(blocked.repoRoot, dryRun);
|
|
3135
|
-
|
|
3136
1915
|
execution.omxScaffoldSync = summarizeDoctorOmxScaffoldSync(blocked.repoRoot, dryRun);
|
|
3137
1916
|
execution.lockSync = syncDoctorLockRegistryAfterMerge(
|
|
3138
1917
|
blocked.repoRoot,
|
|
@@ -3249,7 +2028,6 @@ function emitDoctorSandboxConsoleOutput(options, blocked, metadata, startResult,
|
|
|
3249
2028
|
if (execution.finish.stdout) process.stdout.write(execution.finish.stdout);
|
|
3250
2029
|
if (execution.finish.stderr) process.stderr.write(execution.finish.stderr);
|
|
3251
2030
|
} else if (execution.finish.status === 'failed') {
|
|
3252
|
-
console.log(`[${TOOL_NAME}] Auto-finish flow failed for sandbox branch '${metadata.branch}'.`);
|
|
3253
2031
|
console.log(`[${TOOL_NAME}] Auto-finish flow failed for sandbox branch '${metadata.branch}'.`);
|
|
3254
2032
|
if (execution.finish.stdout) process.stdout.write(execution.finish.stdout);
|
|
3255
2033
|
if (execution.finish.stderr) process.stderr.write(execution.finish.stderr);
|
|
@@ -3399,171 +2177,6 @@ function runSetupInSandbox(options, blocked, repoLabel = '') {
|
|
|
3399
2177
|
};
|
|
3400
2178
|
}
|
|
3401
2179
|
|
|
3402
|
-
function parseTargetFlag(rawArgs, defaultTarget = process.cwd()) {
|
|
3403
|
-
const remaining = [];
|
|
3404
|
-
let target = defaultTarget;
|
|
3405
|
-
|
|
3406
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
3407
|
-
const arg = rawArgs[index];
|
|
3408
|
-
if (arg === '--target') {
|
|
3409
|
-
const next = rawArgs[index + 1];
|
|
3410
|
-
if (!next) {
|
|
3411
|
-
throw new Error('--target requires a path value');
|
|
3412
|
-
}
|
|
3413
|
-
target = next;
|
|
3414
|
-
index += 1;
|
|
3415
|
-
continue;
|
|
3416
|
-
}
|
|
3417
|
-
remaining.push(arg);
|
|
3418
|
-
}
|
|
3419
|
-
|
|
3420
|
-
return { target, args: remaining };
|
|
3421
|
-
}
|
|
3422
|
-
|
|
3423
|
-
function parseReviewArgs(rawArgs) {
|
|
3424
|
-
const parsed = parseTargetFlag(rawArgs, process.cwd());
|
|
3425
|
-
const passthroughArgs = [...parsed.args];
|
|
3426
|
-
if (passthroughArgs[0] === 'start') {
|
|
3427
|
-
passthroughArgs.shift();
|
|
3428
|
-
}
|
|
3429
|
-
return {
|
|
3430
|
-
target: parsed.target,
|
|
3431
|
-
passthroughArgs,
|
|
3432
|
-
};
|
|
3433
|
-
}
|
|
3434
|
-
|
|
3435
|
-
function parseAgentsArgs(rawArgs) {
|
|
3436
|
-
const parsed = parseTargetFlag(rawArgs, process.cwd());
|
|
3437
|
-
const [subcommandRaw = '', ...rest] = parsed.args;
|
|
3438
|
-
const subcommand = subcommandRaw || 'status';
|
|
3439
|
-
const options = {
|
|
3440
|
-
target: parsed.target,
|
|
3441
|
-
subcommand,
|
|
3442
|
-
reviewIntervalSeconds: 30,
|
|
3443
|
-
cleanupIntervalSeconds: 60,
|
|
3444
|
-
idleMinutes: DEFAULT_SHADOW_CLEANUP_IDLE_MINUTES,
|
|
3445
|
-
};
|
|
3446
|
-
|
|
3447
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
3448
|
-
const arg = rest[index];
|
|
3449
|
-
if (arg === '--review-interval') {
|
|
3450
|
-
const next = rest[index + 1];
|
|
3451
|
-
if (!next) {
|
|
3452
|
-
throw new Error('--review-interval requires an integer seconds value');
|
|
3453
|
-
}
|
|
3454
|
-
const parsedValue = Number.parseInt(next, 10);
|
|
3455
|
-
if (!Number.isInteger(parsedValue) || parsedValue < 5) {
|
|
3456
|
-
throw new Error('--review-interval must be an integer >= 5 seconds');
|
|
3457
|
-
}
|
|
3458
|
-
options.reviewIntervalSeconds = parsedValue;
|
|
3459
|
-
index += 1;
|
|
3460
|
-
continue;
|
|
3461
|
-
}
|
|
3462
|
-
if (arg === '--cleanup-interval') {
|
|
3463
|
-
const next = rest[index + 1];
|
|
3464
|
-
if (!next) {
|
|
3465
|
-
throw new Error('--cleanup-interval requires an integer seconds value');
|
|
3466
|
-
}
|
|
3467
|
-
const parsedValue = Number.parseInt(next, 10);
|
|
3468
|
-
if (!Number.isInteger(parsedValue) || parsedValue < 5) {
|
|
3469
|
-
throw new Error('--cleanup-interval must be an integer >= 5 seconds');
|
|
3470
|
-
}
|
|
3471
|
-
options.cleanupIntervalSeconds = parsedValue;
|
|
3472
|
-
index += 1;
|
|
3473
|
-
continue;
|
|
3474
|
-
}
|
|
3475
|
-
if (arg === '--idle-minutes') {
|
|
3476
|
-
const next = rest[index + 1];
|
|
3477
|
-
if (!next) {
|
|
3478
|
-
throw new Error('--idle-minutes requires an integer minutes value');
|
|
3479
|
-
}
|
|
3480
|
-
const parsedValue = Number.parseInt(next, 10);
|
|
3481
|
-
if (!Number.isInteger(parsedValue) || parsedValue < 1) {
|
|
3482
|
-
throw new Error('--idle-minutes must be an integer >= 1');
|
|
3483
|
-
}
|
|
3484
|
-
options.idleMinutes = parsedValue;
|
|
3485
|
-
index += 1;
|
|
3486
|
-
continue;
|
|
3487
|
-
}
|
|
3488
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
if (!['start', 'stop', 'status'].includes(options.subcommand)) {
|
|
3492
|
-
throw new Error(`Unknown agents subcommand: ${options.subcommand}`);
|
|
3493
|
-
}
|
|
3494
|
-
|
|
3495
|
-
return options;
|
|
3496
|
-
}
|
|
3497
|
-
|
|
3498
|
-
function parseReportArgs(rawArgs) {
|
|
3499
|
-
const options = {
|
|
3500
|
-
target: process.cwd(),
|
|
3501
|
-
subcommand: '',
|
|
3502
|
-
repo: '',
|
|
3503
|
-
scorecardJson: '',
|
|
3504
|
-
outputDir: '',
|
|
3505
|
-
date: '',
|
|
3506
|
-
dryRun: false,
|
|
3507
|
-
json: false,
|
|
3508
|
-
};
|
|
3509
|
-
|
|
3510
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
3511
|
-
const arg = rawArgs[index];
|
|
3512
|
-
if (arg === '--target') {
|
|
3513
|
-
const next = rawArgs[index + 1];
|
|
3514
|
-
if (!next) throw new Error('--target requires a path value');
|
|
3515
|
-
options.target = next;
|
|
3516
|
-
index += 1;
|
|
3517
|
-
continue;
|
|
3518
|
-
}
|
|
3519
|
-
if (arg === '--repo') {
|
|
3520
|
-
const next = rawArgs[index + 1];
|
|
3521
|
-
if (!next) throw new Error('--repo requires a value like github.com/owner/repo');
|
|
3522
|
-
options.repo = next;
|
|
3523
|
-
index += 1;
|
|
3524
|
-
continue;
|
|
3525
|
-
}
|
|
3526
|
-
if (arg === '--scorecard-json') {
|
|
3527
|
-
const next = rawArgs[index + 1];
|
|
3528
|
-
if (!next) throw new Error('--scorecard-json requires a path value');
|
|
3529
|
-
options.scorecardJson = next;
|
|
3530
|
-
index += 1;
|
|
3531
|
-
continue;
|
|
3532
|
-
}
|
|
3533
|
-
if (arg === '--output-dir') {
|
|
3534
|
-
const next = rawArgs[index + 1];
|
|
3535
|
-
if (!next) throw new Error('--output-dir requires a path value');
|
|
3536
|
-
options.outputDir = next;
|
|
3537
|
-
index += 1;
|
|
3538
|
-
continue;
|
|
3539
|
-
}
|
|
3540
|
-
if (arg === '--date') {
|
|
3541
|
-
const next = rawArgs[index + 1];
|
|
3542
|
-
if (!next) throw new Error('--date requires a YYYY-MM-DD value');
|
|
3543
|
-
options.date = next;
|
|
3544
|
-
index += 1;
|
|
3545
|
-
continue;
|
|
3546
|
-
}
|
|
3547
|
-
if (arg === '--dry-run') {
|
|
3548
|
-
options.dryRun = true;
|
|
3549
|
-
continue;
|
|
3550
|
-
}
|
|
3551
|
-
if (arg === '--json') {
|
|
3552
|
-
options.json = true;
|
|
3553
|
-
continue;
|
|
3554
|
-
}
|
|
3555
|
-
if (arg.startsWith('-')) {
|
|
3556
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
3557
|
-
}
|
|
3558
|
-
if (!options.subcommand) {
|
|
3559
|
-
options.subcommand = arg;
|
|
3560
|
-
continue;
|
|
3561
|
-
}
|
|
3562
|
-
throw new Error(`Unexpected argument: ${arg}`);
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
return options;
|
|
3566
|
-
}
|
|
3567
2180
|
|
|
3568
2181
|
function todayDateStamp() {
|
|
3569
2182
|
return new Date().toISOString().slice(0, 10);
|
|
@@ -4167,430 +2780,35 @@ function ensureOriginBaseRef(repoRoot, baseBranch) {
|
|
|
4167
2780
|
if (hasRemoteBase.status !== 0) {
|
|
4168
2781
|
throw new Error(`Remote base branch not found: origin/${baseBranch}`);
|
|
4169
2782
|
}
|
|
4170
|
-
}
|
|
4171
|
-
|
|
4172
|
-
function aheadBehind(repoRoot, branchRef, baseRef) {
|
|
4173
|
-
const result = gitRun(repoRoot, ['rev-list', '--left-right', '--count', `${branchRef}...${baseRef}`], {
|
|
4174
|
-
allowFailure: true,
|
|
4175
|
-
});
|
|
4176
|
-
if (result.status !== 0) {
|
|
4177
|
-
throw new Error(`Unable to compute ahead/behind for ${branchRef} vs ${baseRef}`);
|
|
4178
|
-
}
|
|
4179
|
-
const parts = (result.stdout || '').trim().split(/\s+/).filter(Boolean);
|
|
4180
|
-
const ahead = Number.parseInt(parts[0] || '0', 10);
|
|
4181
|
-
const behind = Number.parseInt(parts[1] || '0', 10);
|
|
4182
|
-
return { ahead: Number.isFinite(ahead) ? ahead : 0, behind: Number.isFinite(behind) ? behind : 0 };
|
|
4183
|
-
}
|
|
4184
|
-
|
|
4185
|
-
function lockRegistryStatus(repoRoot) {
|
|
4186
|
-
const result = gitRun(repoRoot, ['status', '--porcelain', '--', LOCK_FILE_RELATIVE], { allowFailure: true });
|
|
4187
|
-
if (result.status !== 0) {
|
|
4188
|
-
return { dirty: false, untracked: false };
|
|
4189
|
-
}
|
|
4190
|
-
const lines = (result.stdout || '').split('\n').filter((line) => line.length > 0);
|
|
4191
|
-
if (lines.length === 0) {
|
|
4192
|
-
return { dirty: false, untracked: false };
|
|
4193
|
-
}
|
|
4194
|
-
const untracked = lines.some((line) => line.startsWith('??'));
|
|
4195
|
-
return { dirty: true, untracked };
|
|
4196
|
-
}
|
|
4197
|
-
|
|
4198
|
-
function parseSyncArgs(rawArgs) {
|
|
4199
|
-
const options = {
|
|
4200
|
-
target: process.cwd(),
|
|
4201
|
-
check: false,
|
|
4202
|
-
base: '',
|
|
4203
|
-
strategy: '',
|
|
4204
|
-
ffOnly: false,
|
|
4205
|
-
dryRun: false,
|
|
4206
|
-
json: false,
|
|
4207
|
-
allAgentBranches: false,
|
|
4208
|
-
allowNonAgent: false,
|
|
4209
|
-
allowDirty: false,
|
|
4210
|
-
};
|
|
4211
|
-
|
|
4212
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
4213
|
-
const arg = rawArgs[index];
|
|
4214
|
-
if (arg === '--target') {
|
|
4215
|
-
const next = rawArgs[index + 1];
|
|
4216
|
-
if (!next) {
|
|
4217
|
-
throw new Error('--target requires a path value');
|
|
4218
|
-
}
|
|
4219
|
-
options.target = next;
|
|
4220
|
-
index += 1;
|
|
4221
|
-
continue;
|
|
4222
|
-
}
|
|
4223
|
-
if (arg === '--base') {
|
|
4224
|
-
const next = rawArgs[index + 1];
|
|
4225
|
-
if (!next) {
|
|
4226
|
-
throw new Error('--base requires a branch value');
|
|
4227
|
-
}
|
|
4228
|
-
options.base = next;
|
|
4229
|
-
index += 1;
|
|
4230
|
-
continue;
|
|
4231
|
-
}
|
|
4232
|
-
if (arg === '--strategy') {
|
|
4233
|
-
const next = rawArgs[index + 1];
|
|
4234
|
-
if (!next) {
|
|
4235
|
-
throw new Error('--strategy requires a value (rebase|merge)');
|
|
4236
|
-
}
|
|
4237
|
-
options.strategy = next;
|
|
4238
|
-
index += 1;
|
|
4239
|
-
continue;
|
|
4240
|
-
}
|
|
4241
|
-
if (arg === '--check') {
|
|
4242
|
-
options.check = true;
|
|
4243
|
-
continue;
|
|
4244
|
-
}
|
|
4245
|
-
if (arg === '--ff-only') {
|
|
4246
|
-
options.ffOnly = true;
|
|
4247
|
-
continue;
|
|
4248
|
-
}
|
|
4249
|
-
if (arg === '--dry-run') {
|
|
4250
|
-
options.dryRun = true;
|
|
4251
|
-
continue;
|
|
4252
|
-
}
|
|
4253
|
-
if (arg === '--json') {
|
|
4254
|
-
options.json = true;
|
|
4255
|
-
continue;
|
|
4256
|
-
}
|
|
4257
|
-
if (arg === '--all-agent-branches') {
|
|
4258
|
-
options.allAgentBranches = true;
|
|
4259
|
-
continue;
|
|
4260
|
-
}
|
|
4261
|
-
if (arg === '--allow-non-agent') {
|
|
4262
|
-
options.allowNonAgent = true;
|
|
4263
|
-
continue;
|
|
4264
|
-
}
|
|
4265
|
-
if (arg === '--allow-dirty') {
|
|
4266
|
-
options.allowDirty = true;
|
|
4267
|
-
continue;
|
|
4268
|
-
}
|
|
4269
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
4270
|
-
}
|
|
4271
|
-
|
|
4272
|
-
return options;
|
|
4273
|
-
}
|
|
4274
|
-
|
|
4275
|
-
function parseCleanupArgs(rawArgs) {
|
|
4276
|
-
const options = {
|
|
4277
|
-
target: process.cwd(),
|
|
4278
|
-
base: '',
|
|
4279
|
-
branch: '',
|
|
4280
|
-
dryRun: false,
|
|
4281
|
-
forceDirty: false,
|
|
4282
|
-
keepRemote: false,
|
|
4283
|
-
keepCleanWorktrees: false,
|
|
4284
|
-
includePrMerged: false,
|
|
4285
|
-
idleMinutes: 0,
|
|
4286
|
-
watch: false,
|
|
4287
|
-
intervalSeconds: 60,
|
|
4288
|
-
once: false,
|
|
4289
|
-
maxBranches: 0,
|
|
4290
|
-
};
|
|
4291
|
-
|
|
4292
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
4293
|
-
const arg = rawArgs[index];
|
|
4294
|
-
if (arg === '--target') {
|
|
4295
|
-
const next = rawArgs[index + 1];
|
|
4296
|
-
if (!next) {
|
|
4297
|
-
throw new Error('--target requires a path value');
|
|
4298
|
-
}
|
|
4299
|
-
options.target = next;
|
|
4300
|
-
index += 1;
|
|
4301
|
-
continue;
|
|
4302
|
-
}
|
|
4303
|
-
if (arg === '--base') {
|
|
4304
|
-
const next = rawArgs[index + 1];
|
|
4305
|
-
if (!next) {
|
|
4306
|
-
throw new Error('--base requires a branch value');
|
|
4307
|
-
}
|
|
4308
|
-
options.base = next;
|
|
4309
|
-
index += 1;
|
|
4310
|
-
continue;
|
|
4311
|
-
}
|
|
4312
|
-
if (arg === '--branch') {
|
|
4313
|
-
const next = rawArgs[index + 1];
|
|
4314
|
-
if (!next) {
|
|
4315
|
-
throw new Error('--branch requires an agent branch value');
|
|
4316
|
-
}
|
|
4317
|
-
options.branch = next;
|
|
4318
|
-
index += 1;
|
|
4319
|
-
continue;
|
|
4320
|
-
}
|
|
4321
|
-
if (arg === '--dry-run') {
|
|
4322
|
-
options.dryRun = true;
|
|
4323
|
-
continue;
|
|
4324
|
-
}
|
|
4325
|
-
if (arg === '--force-dirty') {
|
|
4326
|
-
options.forceDirty = true;
|
|
4327
|
-
continue;
|
|
4328
|
-
}
|
|
4329
|
-
if (arg === '--keep-remote') {
|
|
4330
|
-
options.keepRemote = true;
|
|
4331
|
-
continue;
|
|
4332
|
-
}
|
|
4333
|
-
if (arg === '--keep-clean-worktrees') {
|
|
4334
|
-
options.keepCleanWorktrees = true;
|
|
4335
|
-
continue;
|
|
4336
|
-
}
|
|
4337
|
-
if (arg === '--include-pr-merged') {
|
|
4338
|
-
options.includePrMerged = true;
|
|
4339
|
-
continue;
|
|
4340
|
-
}
|
|
4341
|
-
if (arg === '--idle-minutes') {
|
|
4342
|
-
const next = rawArgs[index + 1];
|
|
4343
|
-
if (!next) {
|
|
4344
|
-
throw new Error('--idle-minutes requires an integer value');
|
|
4345
|
-
}
|
|
4346
|
-
const parsed = Number.parseInt(next, 10);
|
|
4347
|
-
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
4348
|
-
throw new Error('--idle-minutes must be an integer >= 0');
|
|
4349
|
-
}
|
|
4350
|
-
options.idleMinutes = parsed;
|
|
4351
|
-
index += 1;
|
|
4352
|
-
continue;
|
|
4353
|
-
}
|
|
4354
|
-
if (arg === '--watch') {
|
|
4355
|
-
options.watch = true;
|
|
4356
|
-
continue;
|
|
4357
|
-
}
|
|
4358
|
-
if (arg === '--interval') {
|
|
4359
|
-
const next = rawArgs[index + 1];
|
|
4360
|
-
if (!next) {
|
|
4361
|
-
throw new Error('--interval requires an integer seconds value');
|
|
4362
|
-
}
|
|
4363
|
-
const parsed = Number.parseInt(next, 10);
|
|
4364
|
-
if (!Number.isInteger(parsed) || parsed < 5) {
|
|
4365
|
-
throw new Error('--interval must be an integer >= 5 seconds');
|
|
4366
|
-
}
|
|
4367
|
-
options.intervalSeconds = parsed;
|
|
4368
|
-
index += 1;
|
|
4369
|
-
continue;
|
|
4370
|
-
}
|
|
4371
|
-
if (arg === '--once') {
|
|
4372
|
-
options.once = true;
|
|
4373
|
-
continue;
|
|
4374
|
-
}
|
|
4375
|
-
if (arg === '--max-branches') {
|
|
4376
|
-
const next = rawArgs[index + 1];
|
|
4377
|
-
if (!next) {
|
|
4378
|
-
throw new Error('--max-branches requires an integer value');
|
|
4379
|
-
}
|
|
4380
|
-
const parsed = Number.parseInt(next, 10);
|
|
4381
|
-
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
4382
|
-
throw new Error('--max-branches must be an integer >= 1');
|
|
4383
|
-
}
|
|
4384
|
-
options.maxBranches = parsed;
|
|
4385
|
-
index += 1;
|
|
4386
|
-
continue;
|
|
4387
|
-
}
|
|
4388
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
4389
|
-
}
|
|
4390
|
-
|
|
4391
|
-
if (options.watch && options.idleMinutes === 0) {
|
|
4392
|
-
options.idleMinutes = DEFAULT_SHADOW_CLEANUP_IDLE_MINUTES;
|
|
4393
|
-
}
|
|
4394
|
-
|
|
4395
|
-
return options;
|
|
4396
|
-
}
|
|
4397
|
-
|
|
4398
|
-
function parseMergeArgs(rawArgs) {
|
|
4399
|
-
const options = {
|
|
4400
|
-
target: process.cwd(),
|
|
4401
|
-
base: '',
|
|
4402
|
-
into: '',
|
|
4403
|
-
branches: [],
|
|
4404
|
-
task: '',
|
|
4405
|
-
agent: '',
|
|
4406
|
-
};
|
|
4407
|
-
|
|
4408
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
4409
|
-
const arg = rawArgs[index];
|
|
4410
|
-
if (arg === '--target') {
|
|
4411
|
-
const next = rawArgs[index + 1];
|
|
4412
|
-
if (!next) {
|
|
4413
|
-
throw new Error('--target requires a path value');
|
|
4414
|
-
}
|
|
4415
|
-
options.target = next;
|
|
4416
|
-
index += 1;
|
|
4417
|
-
continue;
|
|
4418
|
-
}
|
|
4419
|
-
if (arg === '--base') {
|
|
4420
|
-
const next = rawArgs[index + 1];
|
|
4421
|
-
if (!next) {
|
|
4422
|
-
throw new Error('--base requires a branch value');
|
|
4423
|
-
}
|
|
4424
|
-
options.base = next;
|
|
4425
|
-
index += 1;
|
|
4426
|
-
continue;
|
|
4427
|
-
}
|
|
4428
|
-
if (arg === '--into') {
|
|
4429
|
-
const next = rawArgs[index + 1];
|
|
4430
|
-
if (!next) {
|
|
4431
|
-
throw new Error('--into requires an agent/* branch value');
|
|
4432
|
-
}
|
|
4433
|
-
options.into = next;
|
|
4434
|
-
index += 1;
|
|
4435
|
-
continue;
|
|
4436
|
-
}
|
|
4437
|
-
if (arg === '--branch') {
|
|
4438
|
-
const next = rawArgs[index + 1];
|
|
4439
|
-
if (!next) {
|
|
4440
|
-
throw new Error('--branch requires an agent/* branch value');
|
|
4441
|
-
}
|
|
4442
|
-
options.branches.push(next);
|
|
4443
|
-
index += 1;
|
|
4444
|
-
continue;
|
|
4445
|
-
}
|
|
4446
|
-
if (arg === '--task') {
|
|
4447
|
-
const next = rawArgs[index + 1];
|
|
4448
|
-
if (!next) {
|
|
4449
|
-
throw new Error('--task requires a task value');
|
|
4450
|
-
}
|
|
4451
|
-
options.task = next;
|
|
4452
|
-
index += 1;
|
|
4453
|
-
continue;
|
|
4454
|
-
}
|
|
4455
|
-
if (arg === '--agent') {
|
|
4456
|
-
const next = rawArgs[index + 1];
|
|
4457
|
-
if (!next) {
|
|
4458
|
-
throw new Error('--agent requires an agent value');
|
|
4459
|
-
}
|
|
4460
|
-
options.agent = next;
|
|
4461
|
-
index += 1;
|
|
4462
|
-
continue;
|
|
4463
|
-
}
|
|
4464
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
4465
|
-
}
|
|
2783
|
+
}
|
|
4466
2784
|
|
|
4467
|
-
|
|
4468
|
-
|
|
2785
|
+
function aheadBehind(repoRoot, branchRef, baseRef) {
|
|
2786
|
+
const result = gitRun(repoRoot, ['rev-list', '--left-right', '--count', `${branchRef}...${baseRef}`], {
|
|
2787
|
+
allowFailure: true,
|
|
2788
|
+
});
|
|
2789
|
+
if (result.status !== 0) {
|
|
2790
|
+
throw new Error(`Unable to compute ahead/behind for ${branchRef} vs ${baseRef}`);
|
|
4469
2791
|
}
|
|
4470
|
-
|
|
4471
|
-
|
|
2792
|
+
const parts = (result.stdout || '').trim().split(/\s+/).filter(Boolean);
|
|
2793
|
+
const ahead = Number.parseInt(parts[0] || '0', 10);
|
|
2794
|
+
const behind = Number.parseInt(parts[1] || '0', 10);
|
|
2795
|
+
return { ahead: Number.isFinite(ahead) ? ahead : 0, behind: Number.isFinite(behind) ? behind : 0 };
|
|
4472
2796
|
}
|
|
4473
2797
|
|
|
4474
|
-
function
|
|
4475
|
-
const
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
branch: '',
|
|
4479
|
-
all: false,
|
|
4480
|
-
dryRun: false,
|
|
4481
|
-
waitForMerge: defaults.waitForMerge ?? true,
|
|
4482
|
-
cleanup: defaults.cleanup ?? true,
|
|
4483
|
-
keepRemote: false,
|
|
4484
|
-
noAutoCommit: false,
|
|
4485
|
-
failFast: false,
|
|
4486
|
-
commitMessage: '',
|
|
4487
|
-
mergeMode: defaults.mergeMode || 'pr',
|
|
4488
|
-
};
|
|
4489
|
-
|
|
4490
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
4491
|
-
const arg = rawArgs[index];
|
|
4492
|
-
if (arg === '--target') {
|
|
4493
|
-
const next = rawArgs[index + 1];
|
|
4494
|
-
if (!next) {
|
|
4495
|
-
throw new Error('--target requires a path value');
|
|
4496
|
-
}
|
|
4497
|
-
options.target = next;
|
|
4498
|
-
index += 1;
|
|
4499
|
-
continue;
|
|
4500
|
-
}
|
|
4501
|
-
if (arg === '--base') {
|
|
4502
|
-
const next = rawArgs[index + 1];
|
|
4503
|
-
if (!next) {
|
|
4504
|
-
throw new Error('--base requires a branch value');
|
|
4505
|
-
}
|
|
4506
|
-
options.base = next;
|
|
4507
|
-
index += 1;
|
|
4508
|
-
continue;
|
|
4509
|
-
}
|
|
4510
|
-
if (arg === '--branch') {
|
|
4511
|
-
const next = rawArgs[index + 1];
|
|
4512
|
-
if (!next) {
|
|
4513
|
-
throw new Error('--branch requires an agent/* branch value');
|
|
4514
|
-
}
|
|
4515
|
-
options.branch = next;
|
|
4516
|
-
index += 1;
|
|
4517
|
-
continue;
|
|
4518
|
-
}
|
|
4519
|
-
if (arg === '--commit-message') {
|
|
4520
|
-
const next = rawArgs[index + 1];
|
|
4521
|
-
if (!next) {
|
|
4522
|
-
throw new Error('--commit-message requires a value');
|
|
4523
|
-
}
|
|
4524
|
-
options.commitMessage = next;
|
|
4525
|
-
index += 1;
|
|
4526
|
-
continue;
|
|
4527
|
-
}
|
|
4528
|
-
if (arg === '--all') {
|
|
4529
|
-
options.all = true;
|
|
4530
|
-
continue;
|
|
4531
|
-
}
|
|
4532
|
-
if (arg === '--dry-run') {
|
|
4533
|
-
options.dryRun = true;
|
|
4534
|
-
continue;
|
|
4535
|
-
}
|
|
4536
|
-
if (arg === '--wait-for-merge') {
|
|
4537
|
-
options.waitForMerge = true;
|
|
4538
|
-
continue;
|
|
4539
|
-
}
|
|
4540
|
-
if (arg === '--no-wait-for-merge') {
|
|
4541
|
-
options.waitForMerge = false;
|
|
4542
|
-
continue;
|
|
4543
|
-
}
|
|
4544
|
-
if (arg === '--via-pr') {
|
|
4545
|
-
options.mergeMode = 'pr';
|
|
4546
|
-
continue;
|
|
4547
|
-
}
|
|
4548
|
-
if (arg === '--direct-only') {
|
|
4549
|
-
options.mergeMode = 'direct';
|
|
4550
|
-
continue;
|
|
4551
|
-
}
|
|
4552
|
-
if (arg === '--mode') {
|
|
4553
|
-
const next = rawArgs[index + 1];
|
|
4554
|
-
if (!next) {
|
|
4555
|
-
throw new Error('--mode requires a value');
|
|
4556
|
-
}
|
|
4557
|
-
if (!['auto', 'direct', 'pr'].includes(next)) {
|
|
4558
|
-
throw new Error(`Invalid --mode value: ${next} (expected auto|direct|pr)`);
|
|
4559
|
-
}
|
|
4560
|
-
options.mergeMode = next;
|
|
4561
|
-
index += 1;
|
|
4562
|
-
continue;
|
|
4563
|
-
}
|
|
4564
|
-
if (arg === '--cleanup') {
|
|
4565
|
-
options.cleanup = true;
|
|
4566
|
-
continue;
|
|
4567
|
-
}
|
|
4568
|
-
if (arg === '--no-cleanup') {
|
|
4569
|
-
options.cleanup = false;
|
|
4570
|
-
continue;
|
|
4571
|
-
}
|
|
4572
|
-
if (arg === '--keep-remote') {
|
|
4573
|
-
options.keepRemote = true;
|
|
4574
|
-
continue;
|
|
4575
|
-
}
|
|
4576
|
-
if (arg === '--no-auto-commit') {
|
|
4577
|
-
options.noAutoCommit = true;
|
|
4578
|
-
continue;
|
|
4579
|
-
}
|
|
4580
|
-
if (arg === '--fail-fast') {
|
|
4581
|
-
options.failFast = true;
|
|
4582
|
-
continue;
|
|
4583
|
-
}
|
|
4584
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
2798
|
+
function lockRegistryStatus(repoRoot) {
|
|
2799
|
+
const result = gitRun(repoRoot, ['status', '--porcelain', '--', LOCK_FILE_RELATIVE], { allowFailure: true });
|
|
2800
|
+
if (result.status !== 0) {
|
|
2801
|
+
return { dirty: false, untracked: false };
|
|
4585
2802
|
}
|
|
4586
|
-
|
|
4587
|
-
if (
|
|
4588
|
-
|
|
2803
|
+
const lines = (result.stdout || '').split('\n').filter((line) => line.length > 0);
|
|
2804
|
+
if (lines.length === 0) {
|
|
2805
|
+
return { dirty: false, untracked: false };
|
|
4589
2806
|
}
|
|
4590
|
-
|
|
4591
|
-
return
|
|
2807
|
+
const untracked = lines.some((line) => line.startsWith('??'));
|
|
2808
|
+
return { dirty: true, untracked };
|
|
4592
2809
|
}
|
|
4593
2810
|
|
|
2811
|
+
|
|
4594
2812
|
function listAgentWorktrees(repoRoot) {
|
|
4595
2813
|
const result = gitRun(repoRoot, ['worktree', 'list', '--porcelain'], { allowFailure: true });
|
|
4596
2814
|
if (result.status !== 0) {
|
|
@@ -4927,31 +3145,6 @@ function readSingleLineFromStdin() {
|
|
|
4927
3145
|
}
|
|
4928
3146
|
}
|
|
4929
3147
|
|
|
4930
|
-
function promptYesNo(question, defaultYes = true) {
|
|
4931
|
-
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
4932
|
-
while (true) {
|
|
4933
|
-
process.stdout.write(`${question} ${hint} `);
|
|
4934
|
-
const answer = readSingleLineFromStdin().trim().toLowerCase();
|
|
4935
|
-
|
|
4936
|
-
if (!answer) {
|
|
4937
|
-
return defaultYes;
|
|
4938
|
-
}
|
|
4939
|
-
if (answer === 'y' || answer === 'yes') {
|
|
4940
|
-
return true;
|
|
4941
|
-
}
|
|
4942
|
-
if (answer === 'n' || answer === 'no') {
|
|
4943
|
-
return false;
|
|
4944
|
-
}
|
|
4945
|
-
process.stdout.write('Please answer with y or n.\n');
|
|
4946
|
-
}
|
|
4947
|
-
}
|
|
4948
|
-
|
|
4949
|
-
function envFlagEnabled(name) {
|
|
4950
|
-
const raw = process.env[name];
|
|
4951
|
-
if (raw == null) return false;
|
|
4952
|
-
return ['1', 'true', 'yes', 'on'].includes(String(raw).trim().toLowerCase());
|
|
4953
|
-
}
|
|
4954
|
-
|
|
4955
3148
|
function parseAutoApproval(name) {
|
|
4956
3149
|
const raw = process.env[name];
|
|
4957
3150
|
if (raw == null) return null;
|
|
@@ -5079,11 +3272,11 @@ function parseNpmVersionOutput(stdout) {
|
|
|
5079
3272
|
}
|
|
5080
3273
|
|
|
5081
3274
|
function checkForGuardexUpdate() {
|
|
5082
|
-
if (
|
|
3275
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_UPDATE_CHECK)) {
|
|
5083
3276
|
return { checked: false, reason: 'disabled' };
|
|
5084
3277
|
}
|
|
5085
3278
|
|
|
5086
|
-
const forceCheck =
|
|
3279
|
+
const forceCheck = envFlagIsTruthy(process.env.GUARDEX_FORCE_UPDATE_CHECK);
|
|
5087
3280
|
if (!forceCheck && !isInteractiveTerminal()) {
|
|
5088
3281
|
return { checked: false, reason: 'non-interactive' };
|
|
5089
3282
|
}
|
|
@@ -5203,11 +3396,11 @@ function restartIntoUpdatedGuardex(expectedVersion) {
|
|
|
5203
3396
|
}
|
|
5204
3397
|
|
|
5205
3398
|
function checkForOpenSpecPackageUpdate() {
|
|
5206
|
-
if (
|
|
3399
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_OPENSPEC_UPDATE_CHECK)) {
|
|
5207
3400
|
return { checked: false, reason: 'disabled' };
|
|
5208
3401
|
}
|
|
5209
3402
|
|
|
5210
|
-
const forceCheck =
|
|
3403
|
+
const forceCheck = envFlagIsTruthy(process.env.GUARDEX_FORCE_OPENSPEC_UPDATE_CHECK);
|
|
5211
3404
|
if (!forceCheck && !isInteractiveTerminal()) {
|
|
5212
3405
|
return { checked: false, reason: 'non-interactive' };
|
|
5213
3406
|
}
|
|
@@ -5432,10 +3625,6 @@ function installGlobalToolchain(options) {
|
|
|
5432
3625
|
return getToolchainApi().installGlobalToolchain(options);
|
|
5433
3626
|
}
|
|
5434
3627
|
|
|
5435
|
-
function gitRefExists(repoRoot, refName) {
|
|
5436
|
-
return gitRun(repoRoot, ['show-ref', '--verify', '--quiet', refName], { allowFailure: true }).status === 0;
|
|
5437
|
-
}
|
|
5438
|
-
|
|
5439
3628
|
function findStaleLockPaths(repoRoot, locks) {
|
|
5440
3629
|
const stale = [];
|
|
5441
3630
|
|
|
@@ -5716,21 +3905,6 @@ function runScanInternal(options) {
|
|
|
5716
3905
|
};
|
|
5717
3906
|
}
|
|
5718
3907
|
|
|
5719
|
-
function printOperations(title, payload, dryRun = false) {
|
|
5720
|
-
console.log(`[${TOOL_NAME}] ${title}: ${payload.repoRoot}`);
|
|
5721
|
-
for (const operation of payload.operations) {
|
|
5722
|
-
const note = operation.note ? ` (${operation.note})` : '';
|
|
5723
|
-
console.log(` - ${operation.status.padEnd(12)} ${operation.file}${note}`);
|
|
5724
|
-
}
|
|
5725
|
-
console.log(
|
|
5726
|
-
` - hooksPath ${payload.hookResult.status} ${payload.hookResult.key}=${payload.hookResult.value}`,
|
|
5727
|
-
);
|
|
5728
|
-
|
|
5729
|
-
if (dryRun) {
|
|
5730
|
-
console.log(`[${TOOL_NAME}] Dry run complete. No files were modified.`);
|
|
5731
|
-
}
|
|
5732
|
-
}
|
|
5733
|
-
|
|
5734
3908
|
function printScanResult(scan, json = false) {
|
|
5735
3909
|
if (json) {
|
|
5736
3910
|
process.stdout.write(
|
|
@@ -6046,17 +4220,18 @@ function doctor(rawArgs) {
|
|
|
6046
4220
|
const topRepoRoot = resolveRepoRoot(options.target);
|
|
6047
4221
|
const discoveredRepos = options.recursive
|
|
6048
4222
|
? discoverNestedGitRepos(topRepoRoot, {
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
4223
|
+
maxDepth: options.nestedMaxDepth,
|
|
4224
|
+
extraSkip: options.nestedSkipDirs,
|
|
4225
|
+
includeSubmodules: options.includeSubmodules,
|
|
4226
|
+
skipRelativeDirs: AGENT_WORKTREE_RELATIVE_DIRS,
|
|
4227
|
+
})
|
|
6053
4228
|
: [topRepoRoot];
|
|
6054
4229
|
|
|
6055
4230
|
if (discoveredRepos.length > 1) {
|
|
6056
4231
|
if (!options.json) {
|
|
6057
4232
|
console.log(
|
|
6058
4233
|
`[${TOOL_NAME}] Detected ${discoveredRepos.length} git repos under ${topRepoRoot}. ` +
|
|
6059
|
-
`Repairing each with doctor (use --single-repo to limit to the target).`,
|
|
4234
|
+
`Repairing each with doctor (use --single-repo or --current to limit to the target).`,
|
|
6060
4235
|
);
|
|
6061
4236
|
}
|
|
6062
4237
|
|
|
@@ -6680,12 +4855,13 @@ function setup(rawArgs) {
|
|
|
6680
4855
|
maxDepth: options.nestedMaxDepth,
|
|
6681
4856
|
extraSkip: options.nestedSkipDirs,
|
|
6682
4857
|
includeSubmodules: options.includeSubmodules,
|
|
4858
|
+
skipRelativeDirs: AGENT_WORKTREE_RELATIVE_DIRS,
|
|
6683
4859
|
})
|
|
6684
4860
|
: [topRepoRoot];
|
|
6685
4861
|
|
|
6686
4862
|
if (discoveredRepos.length > 1) {
|
|
6687
4863
|
console.log(
|
|
6688
|
-
`[${TOOL_NAME}] Detected ${discoveredRepos.length} git repos under ${topRepoRoot}. Installing into each (use --no-recursive to limit to the top-level).`,
|
|
4864
|
+
`[${TOOL_NAME}] Detected ${discoveredRepos.length} git repos under ${topRepoRoot}. Installing into each (use --no-recursive or --current to limit to the top-level).`,
|
|
6689
4865
|
);
|
|
6690
4866
|
for (const repoPath of discoveredRepos) {
|
|
6691
4867
|
const marker = repoPath === topRepoRoot ? ' (top-level)' : '';
|
|
@@ -6733,16 +4909,9 @@ function setup(rawArgs) {
|
|
|
6733
4909
|
dryRun: perRepoOptions.dryRun,
|
|
6734
4910
|
});
|
|
6735
4911
|
printScanResult(scanResult, false);
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
);
|
|
6740
|
-
for (const detail of autoFinishSummary.details) {
|
|
6741
|
-
console.log(`[${TOOL_NAME}] ${detail}`);
|
|
6742
|
-
}
|
|
6743
|
-
} else if (autoFinishSummary.details.length > 0) {
|
|
6744
|
-
console.log(`[${TOOL_NAME}] ${autoFinishSummary.details[0]}`);
|
|
6745
|
-
}
|
|
4912
|
+
printAutoFinishSummary(autoFinishSummary, {
|
|
4913
|
+
baseBranch: currentBaseBranch,
|
|
4914
|
+
});
|
|
6746
4915
|
printSetupRepoHints(scanResult.repoRoot, currentBaseBranch, repoLabel);
|
|
6747
4916
|
|
|
6748
4917
|
aggregateErrors += scanResult.errors;
|
|
@@ -7039,247 +5208,6 @@ function release(rawArgs) {
|
|
|
7039
5208
|
process.exitCode = 0;
|
|
7040
5209
|
}
|
|
7041
5210
|
|
|
7042
|
-
function installMany(rawArgs) {
|
|
7043
|
-
const options = parseInstallManyArgs(rawArgs);
|
|
7044
|
-
const targets = collectInstallManyTargets(options);
|
|
7045
|
-
|
|
7046
|
-
if (!targets.length) {
|
|
7047
|
-
throw new Error('install-many did not find any targets to process.');
|
|
7048
|
-
}
|
|
7049
|
-
|
|
7050
|
-
if (options.usedImplicitWorkspaceDefault) {
|
|
7051
|
-
console.log(
|
|
7052
|
-
`[multiagent-safety] No explicit targets provided. Defaulting to workspace scan: ${path.resolve(
|
|
7053
|
-
options.workspace,
|
|
7054
|
-
)} (max depth ${options.maxDepth})`,
|
|
7055
|
-
);
|
|
7056
|
-
}
|
|
7057
|
-
|
|
7058
|
-
console.log(
|
|
7059
|
-
`[multiagent-safety] install-many starting for ${targets.length} target path(s)${
|
|
7060
|
-
options.dryRun ? ' [dry-run]' : ''
|
|
7061
|
-
}`,
|
|
7062
|
-
);
|
|
7063
|
-
|
|
7064
|
-
let installed = 0;
|
|
7065
|
-
let duplicateRepos = 0;
|
|
7066
|
-
const seenRepoRoots = new Set();
|
|
7067
|
-
const failures = [];
|
|
7068
|
-
|
|
7069
|
-
for (const targetPath of targets) {
|
|
7070
|
-
let repoRoot;
|
|
7071
|
-
try {
|
|
7072
|
-
repoRoot = resolveRepoRoot(targetPath);
|
|
7073
|
-
} catch (error) {
|
|
7074
|
-
failures.push({ target: targetPath, message: error.message });
|
|
7075
|
-
if (options.failFast) {
|
|
7076
|
-
break;
|
|
7077
|
-
}
|
|
7078
|
-
continue;
|
|
7079
|
-
}
|
|
7080
|
-
|
|
7081
|
-
if (seenRepoRoots.has(repoRoot)) {
|
|
7082
|
-
duplicateRepos += 1;
|
|
7083
|
-
console.log(`[multiagent-safety] Skipping duplicate repo target: ${targetPath} -> ${repoRoot}`);
|
|
7084
|
-
continue;
|
|
7085
|
-
}
|
|
7086
|
-
|
|
7087
|
-
seenRepoRoots.add(repoRoot);
|
|
7088
|
-
|
|
7089
|
-
try {
|
|
7090
|
-
const report = installIntoRepoRoot(repoRoot, options);
|
|
7091
|
-
printInstallReport(report);
|
|
7092
|
-
installed += 1;
|
|
7093
|
-
} catch (error) {
|
|
7094
|
-
failures.push({ target: repoRoot, message: error.message });
|
|
7095
|
-
if (options.failFast) {
|
|
7096
|
-
break;
|
|
7097
|
-
}
|
|
7098
|
-
}
|
|
7099
|
-
}
|
|
7100
|
-
|
|
7101
|
-
console.log(
|
|
7102
|
-
`[multiagent-safety] install-many summary: installed=${installed}, failures=${failures.length}, duplicate-targets=${duplicateRepos}`,
|
|
7103
|
-
);
|
|
7104
|
-
|
|
7105
|
-
if (failures.length > 0) {
|
|
7106
|
-
console.error('[multiagent-safety] Failed targets:');
|
|
7107
|
-
for (const failure of failures) {
|
|
7108
|
-
console.error(` - ${failure.target}`);
|
|
7109
|
-
console.error(` ${failure.message}`);
|
|
7110
|
-
}
|
|
7111
|
-
throw new Error(`install-many completed with ${failures.length} failure(s)`);
|
|
7112
|
-
}
|
|
7113
|
-
|
|
7114
|
-
if (options.dryRun) {
|
|
7115
|
-
console.log('[multiagent-safety] Dry run complete. No files were modified.');
|
|
7116
|
-
} else {
|
|
7117
|
-
console.log('[multiagent-safety] Installed multi-agent safety workflow across all targets.');
|
|
7118
|
-
}
|
|
7119
|
-
}
|
|
7120
|
-
|
|
7121
|
-
function initWorkspace(rawArgs) {
|
|
7122
|
-
const options = parseInitWorkspaceArgs(rawArgs);
|
|
7123
|
-
const resolvedWorkspace = path.resolve(options.workspace);
|
|
7124
|
-
const repos = discoverGitRepos(resolvedWorkspace, options.maxDepth)
|
|
7125
|
-
.map((repoPath) => path.resolve(repoPath))
|
|
7126
|
-
.sort();
|
|
7127
|
-
|
|
7128
|
-
const outputPath = options.output
|
|
7129
|
-
? path.resolve(options.output)
|
|
7130
|
-
: path.join(resolvedWorkspace, DEFAULT_WORKSPACE_TARGETS_FILE);
|
|
7131
|
-
|
|
7132
|
-
if (fs.existsSync(outputPath) && !options.force) {
|
|
7133
|
-
throw new Error(`Refusing to overwrite existing file without --force: ${outputPath}`);
|
|
7134
|
-
}
|
|
7135
|
-
|
|
7136
|
-
const headerLines = [
|
|
7137
|
-
'# multiagent-safety workspace targets',
|
|
7138
|
-
`# generated: ${new Date().toISOString()}`,
|
|
7139
|
-
`# workspace: ${resolvedWorkspace}`,
|
|
7140
|
-
`# max-depth: ${options.maxDepth}`,
|
|
7141
|
-
'#',
|
|
7142
|
-
'# Run:',
|
|
7143
|
-
`# multiagent-safety install-many --targets-file "${outputPath}"`,
|
|
7144
|
-
'',
|
|
7145
|
-
];
|
|
7146
|
-
const content = `${headerLines.join('\n')}${repos.join('\n')}${repos.length ? '\n' : ''}`;
|
|
7147
|
-
|
|
7148
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
7149
|
-
fs.writeFileSync(outputPath, content, 'utf8');
|
|
7150
|
-
|
|
7151
|
-
console.log(`[multiagent-safety] Workspace target file written: ${outputPath}`);
|
|
7152
|
-
console.log(`[multiagent-safety] Repos discovered: ${repos.length}`);
|
|
7153
|
-
if (repos.length === 0) {
|
|
7154
|
-
console.log('[multiagent-safety] No git repos found. You can add target paths manually to the file.');
|
|
7155
|
-
} else {
|
|
7156
|
-
console.log(`[multiagent-safety] Next step: multiagent-safety install-many --targets-file "${outputPath}"`);
|
|
7157
|
-
}
|
|
7158
|
-
}
|
|
7159
|
-
|
|
7160
|
-
function doctorAudit(rawArgs) {
|
|
7161
|
-
const options = parseDoctorArgs(rawArgs);
|
|
7162
|
-
const repoRoot = resolveRepoRoot(options.target);
|
|
7163
|
-
const guardexToggle = resolveGuardexRepoToggle(repoRoot);
|
|
7164
|
-
const failures = [];
|
|
7165
|
-
const warnings = [];
|
|
7166
|
-
|
|
7167
|
-
function ok(message) {
|
|
7168
|
-
console.log(` [ok] ${message}`);
|
|
7169
|
-
}
|
|
7170
|
-
function warn(message) {
|
|
7171
|
-
warnings.push(message);
|
|
7172
|
-
console.log(` [warn] ${message}`);
|
|
7173
|
-
}
|
|
7174
|
-
function fail(message) {
|
|
7175
|
-
failures.push(message);
|
|
7176
|
-
console.log(` [fail] ${message}`);
|
|
7177
|
-
}
|
|
7178
|
-
|
|
7179
|
-
console.log(`[multiagent-safety] doctor target: ${repoRoot}`);
|
|
7180
|
-
if (!guardexToggle.enabled) {
|
|
7181
|
-
console.log(
|
|
7182
|
-
`[multiagent-safety] Guardex is disabled for this repo (${describeGuardexRepoToggle(guardexToggle)}).`,
|
|
7183
|
-
);
|
|
7184
|
-
console.log('[multiagent-safety] doctor passed.');
|
|
7185
|
-
return;
|
|
7186
|
-
}
|
|
7187
|
-
|
|
7188
|
-
const hooksPath = run('git', ['-C', repoRoot, 'config', '--get', 'core.hooksPath']);
|
|
7189
|
-
if (hooksPath.status !== 0) {
|
|
7190
|
-
fail('git core.hooksPath is not configured');
|
|
7191
|
-
} else if (hooksPath.stdout.trim() !== '.githooks') {
|
|
7192
|
-
fail(`git core.hooksPath is "${hooksPath.stdout.trim()}" (expected ".githooks")`);
|
|
7193
|
-
} else {
|
|
7194
|
-
ok('git core.hooksPath is .githooks');
|
|
7195
|
-
}
|
|
7196
|
-
|
|
7197
|
-
for (const relativePath of REQUIRED_MANAGED_REPO_FILES) {
|
|
7198
|
-
const absolutePath = path.join(repoRoot, relativePath);
|
|
7199
|
-
if (!fs.existsSync(absolutePath)) {
|
|
7200
|
-
fail(`missing ${relativePath}`);
|
|
7201
|
-
continue;
|
|
7202
|
-
}
|
|
7203
|
-
ok(`found ${relativePath}`);
|
|
7204
|
-
|
|
7205
|
-
if (EXECUTABLE_RELATIVE_PATHS.has(relativePath)) {
|
|
7206
|
-
try {
|
|
7207
|
-
fs.accessSync(absolutePath, fs.constants.X_OK);
|
|
7208
|
-
} catch {
|
|
7209
|
-
fail(`${relativePath} exists but is not executable`);
|
|
7210
|
-
}
|
|
7211
|
-
}
|
|
7212
|
-
}
|
|
7213
|
-
|
|
7214
|
-
const lockFilePath = path.join(repoRoot, '.omx/state/agent-file-locks.json');
|
|
7215
|
-
if (fs.existsSync(lockFilePath)) {
|
|
7216
|
-
try {
|
|
7217
|
-
const parsed = JSON.parse(fs.readFileSync(lockFilePath, 'utf8'));
|
|
7218
|
-
if (!parsed || typeof parsed !== 'object' || typeof parsed.locks !== 'object') {
|
|
7219
|
-
fail('.omx/state/agent-file-locks.json does not contain a valid { locks: {} } object');
|
|
7220
|
-
} else {
|
|
7221
|
-
ok('lock registry JSON is valid');
|
|
7222
|
-
}
|
|
7223
|
-
} catch (error) {
|
|
7224
|
-
fail(`lock registry JSON is invalid: ${error.message}`);
|
|
7225
|
-
}
|
|
7226
|
-
}
|
|
7227
|
-
|
|
7228
|
-
const packagePath = path.join(repoRoot, 'package.json');
|
|
7229
|
-
if (!fs.existsSync(packagePath)) {
|
|
7230
|
-
warn('package.json not found (legacy agent:* script drift cannot be checked)');
|
|
7231
|
-
} else {
|
|
7232
|
-
try {
|
|
7233
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
7234
|
-
const scripts = pkg.scripts || {};
|
|
7235
|
-
const legacyAgentScripts = Object.entries(LEGACY_MANAGED_PACKAGE_SCRIPTS)
|
|
7236
|
-
.filter(([name, expectedValue]) => scripts[name] === expectedValue)
|
|
7237
|
-
.map(([name]) => name);
|
|
7238
|
-
if (legacyAgentScripts.length > 0) {
|
|
7239
|
-
warn(`legacy agent:* package.json scripts remain (${legacyAgentScripts.join(', ')}); run '${SHORT_TOOL_NAME} migrate' to remove them`);
|
|
7240
|
-
} else {
|
|
7241
|
-
ok('package.json does not contain Guardex-managed agent:* helper scripts');
|
|
7242
|
-
}
|
|
7243
|
-
} catch (error) {
|
|
7244
|
-
fail(`package.json is invalid JSON: ${error.message}`);
|
|
7245
|
-
}
|
|
7246
|
-
}
|
|
7247
|
-
|
|
7248
|
-
const agentsPath = path.join(repoRoot, 'AGENTS.md');
|
|
7249
|
-
if (!fs.existsSync(agentsPath)) {
|
|
7250
|
-
warn('AGENTS.md not found (multi-agent contract snippet not present)');
|
|
7251
|
-
} else {
|
|
7252
|
-
const agentsContent = fs.readFileSync(agentsPath, 'utf8');
|
|
7253
|
-
if (!agentsContent.includes(AGENTS_MARKER_START)) {
|
|
7254
|
-
warn('AGENTS.md exists but multiagent-safety snippet marker is missing');
|
|
7255
|
-
} else {
|
|
7256
|
-
ok('AGENTS.md contains multiagent-safety snippet marker');
|
|
7257
|
-
}
|
|
7258
|
-
}
|
|
7259
|
-
|
|
7260
|
-
if (warnings.length) {
|
|
7261
|
-
console.log(`[multiagent-safety] warnings: ${warnings.length}`);
|
|
7262
|
-
}
|
|
7263
|
-
if (failures.length) {
|
|
7264
|
-
console.log(`[multiagent-safety] failures: ${failures.length}`);
|
|
7265
|
-
}
|
|
7266
|
-
|
|
7267
|
-
if (failures.length === 0 && (!options.strict || warnings.length === 0)) {
|
|
7268
|
-
console.log('[multiagent-safety] doctor passed.');
|
|
7269
|
-
if (warnings.length > 0) {
|
|
7270
|
-
console.log('[multiagent-safety] tip: run with --strict to treat warnings as failures.');
|
|
7271
|
-
}
|
|
7272
|
-
return;
|
|
7273
|
-
}
|
|
7274
|
-
|
|
7275
|
-
if (options.strict && warnings.length > 0 && failures.length === 0) {
|
|
7276
|
-
console.log('[multiagent-safety] strict mode failed due to warnings.');
|
|
7277
|
-
} else {
|
|
7278
|
-
console.log('[multiagent-safety] doctor failed.');
|
|
7279
|
-
}
|
|
7280
|
-
throw new Error('doctor detected configuration issues');
|
|
7281
|
-
}
|
|
7282
|
-
|
|
7283
5211
|
function printAgentsSnippet() {
|
|
7284
5212
|
const snippetPath = path.join(TEMPLATE_ROOT, 'AGENTS.multiagent-safety.md');
|
|
7285
5213
|
process.stdout.write(fs.readFileSync(snippetPath, 'utf8'));
|
|
@@ -7320,17 +5248,6 @@ function prompt(rawArgs) {
|
|
|
7320
5248
|
return copyPrompt();
|
|
7321
5249
|
}
|
|
7322
5250
|
|
|
7323
|
-
function printStandaloneOperations(title, rootLabel, operations, dryRun = false) {
|
|
7324
|
-
console.log(`[${TOOL_NAME}] ${title}: ${rootLabel}`);
|
|
7325
|
-
for (const operation of operations) {
|
|
7326
|
-
const note = operation.note ? ` (${operation.note})` : '';
|
|
7327
|
-
console.log(` - ${operation.status.padEnd(12)} ${operation.file}${note}`);
|
|
7328
|
-
}
|
|
7329
|
-
if (dryRun) {
|
|
7330
|
-
console.log(`[${TOOL_NAME}] Dry run complete. No files were modified.`);
|
|
7331
|
-
}
|
|
7332
|
-
}
|
|
7333
|
-
|
|
7334
5251
|
function branch(rawArgs) {
|
|
7335
5252
|
const [subcommand, ...rest] = rawArgs;
|
|
7336
5253
|
if (subcommand === 'start') {
|
|
@@ -7552,78 +5469,6 @@ function protect(rawArgs) {
|
|
|
7552
5469
|
throw new Error(`Unknown protect subcommand: ${subcommand}`);
|
|
7553
5470
|
}
|
|
7554
5471
|
|
|
7555
|
-
function levenshteinDistance(a, b) {
|
|
7556
|
-
const rows = a.length + 1;
|
|
7557
|
-
const cols = b.length + 1;
|
|
7558
|
-
const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
|
|
7559
|
-
|
|
7560
|
-
for (let i = 0; i < rows; i += 1) matrix[i][0] = i;
|
|
7561
|
-
for (let j = 0; j < cols; j += 1) matrix[0][j] = j;
|
|
7562
|
-
|
|
7563
|
-
for (let i = 1; i < rows; i += 1) {
|
|
7564
|
-
for (let j = 1; j < cols; j += 1) {
|
|
7565
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
7566
|
-
matrix[i][j] = Math.min(
|
|
7567
|
-
matrix[i - 1][j] + 1, // deletion
|
|
7568
|
-
matrix[i][j - 1] + 1, // insertion
|
|
7569
|
-
matrix[i - 1][j - 1] + cost, // substitution
|
|
7570
|
-
);
|
|
7571
|
-
}
|
|
7572
|
-
}
|
|
7573
|
-
return matrix[a.length][b.length];
|
|
7574
|
-
}
|
|
7575
|
-
|
|
7576
|
-
function maybeSuggestCommand(command) {
|
|
7577
|
-
let best = null;
|
|
7578
|
-
let bestDistance = Number.POSITIVE_INFINITY;
|
|
7579
|
-
|
|
7580
|
-
for (const candidate of SUGGESTIBLE_COMMANDS) {
|
|
7581
|
-
const dist = levenshteinDistance(command, candidate);
|
|
7582
|
-
if (dist < bestDistance) {
|
|
7583
|
-
bestDistance = dist;
|
|
7584
|
-
best = candidate;
|
|
7585
|
-
}
|
|
7586
|
-
}
|
|
7587
|
-
|
|
7588
|
-
if (best && bestDistance <= 2) {
|
|
7589
|
-
return best;
|
|
7590
|
-
}
|
|
7591
|
-
|
|
7592
|
-
return null;
|
|
7593
|
-
}
|
|
7594
|
-
|
|
7595
|
-
function normalizeCommandOrThrow(command) {
|
|
7596
|
-
if (COMMAND_TYPO_ALIASES.has(command)) {
|
|
7597
|
-
const mapped = COMMAND_TYPO_ALIASES.get(command);
|
|
7598
|
-
console.log(`[${TOOL_NAME}] Interpreting '${command}' as '${mapped}'.`);
|
|
7599
|
-
return mapped;
|
|
7600
|
-
}
|
|
7601
|
-
return command;
|
|
7602
|
-
}
|
|
7603
|
-
|
|
7604
|
-
function warnDeprecatedAlias(aliasName) {
|
|
7605
|
-
const entry = DEPRECATED_COMMAND_ALIASES.get(aliasName);
|
|
7606
|
-
if (!entry) return;
|
|
7607
|
-
console.error(
|
|
7608
|
-
`[${TOOL_NAME}] '${aliasName}' is deprecated and will be removed in a future major release. ` +
|
|
7609
|
-
`Use: ${entry.hint}`,
|
|
7610
|
-
);
|
|
7611
|
-
}
|
|
7612
|
-
|
|
7613
|
-
function extractFlag(args, ...names) {
|
|
7614
|
-
const flagSet = new Set(names);
|
|
7615
|
-
let found = false;
|
|
7616
|
-
const remaining = [];
|
|
7617
|
-
for (const arg of args) {
|
|
7618
|
-
if (flagSet.has(arg)) {
|
|
7619
|
-
found = true;
|
|
7620
|
-
} else {
|
|
7621
|
-
remaining.push(arg);
|
|
7622
|
-
}
|
|
7623
|
-
}
|
|
7624
|
-
return { found, remaining };
|
|
7625
|
-
}
|
|
7626
|
-
|
|
7627
5472
|
function main() {
|
|
7628
5473
|
const args = process.argv.slice(2);
|
|
7629
5474
|
|