@imdeadpool/guardex 6.1.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -39
- package/bin/multiagent-safety.js +372 -300
- package/package.json +3 -5
- package/templates/AGENTS.multiagent-safety.md +9 -82
- package/templates/claude/commands/guardex.md +6 -12
- package/templates/codex/skills/guardex/SKILL.md +18 -64
- package/templates/githooks/post-merge +39 -3
- package/templates/githooks/pre-commit +27 -193
- package/templates/githooks/pre-push +0 -0
- package/templates/scripts/agent-branch-finish.sh +70 -702
- package/templates/scripts/agent-branch-start.sh +76 -877
- package/templates/scripts/agent-worktree-prune.sh +65 -353
- package/templates/scripts/codex-agent.sh +626 -238
- package/templates/scripts/install-agent-git-hooks.sh +4 -27
- package/templates/scripts/openspec/init-change-workspace.sh +4 -50
- package/templates/scripts/openspec/init-plan-workspace.sh +48 -495
- package/templates/scripts/review-bot-watch.sh +11 -11
- package/templates/githooks/post-checkout +0 -68
package/bin/multiagent-safety.js
CHANGED
|
@@ -54,7 +54,6 @@ const TEMPLATE_FILES = [
|
|
|
54
54
|
'githooks/pre-commit',
|
|
55
55
|
'githooks/pre-push',
|
|
56
56
|
'githooks/post-merge',
|
|
57
|
-
'githooks/post-checkout',
|
|
58
57
|
'codex/skills/guardex/SKILL.md',
|
|
59
58
|
'codex/skills/guardex-merge-skills-to-dev/SKILL.md',
|
|
60
59
|
'claude/commands/guardex.md',
|
|
@@ -98,7 +97,6 @@ const EXECUTABLE_RELATIVE_PATHS = new Set([
|
|
|
98
97
|
'.githooks/pre-commit',
|
|
99
98
|
'.githooks/pre-push',
|
|
100
99
|
'.githooks/post-merge',
|
|
101
|
-
'.githooks/post-checkout',
|
|
102
100
|
]);
|
|
103
101
|
|
|
104
102
|
const CRITICAL_GUARDRAIL_PATHS = new Set([
|
|
@@ -106,7 +104,6 @@ const CRITICAL_GUARDRAIL_PATHS = new Set([
|
|
|
106
104
|
'.githooks/pre-commit',
|
|
107
105
|
'.githooks/pre-push',
|
|
108
106
|
'.githooks/post-merge',
|
|
109
|
-
'.githooks/post-checkout',
|
|
110
107
|
'scripts/agent-branch-start.sh',
|
|
111
108
|
'scripts/agent-branch-finish.sh',
|
|
112
109
|
'scripts/agent-worktree-prune.sh',
|
|
@@ -134,7 +131,6 @@ const MANAGED_GITIGNORE_PATHS = [
|
|
|
134
131
|
'.githooks/pre-commit',
|
|
135
132
|
'.githooks/pre-push',
|
|
136
133
|
'.githooks/post-merge',
|
|
137
|
-
'.githooks/post-checkout',
|
|
138
134
|
'oh-my-codex/',
|
|
139
135
|
'.codex/skills/guardex/SKILL.md',
|
|
140
136
|
'.codex/skills/guardex-merge-skills-to-dev/SKILL.md',
|
|
@@ -167,158 +163,84 @@ const COMMAND_TYPO_ALIASES = new Map([
|
|
|
167
163
|
const SUGGESTIBLE_COMMANDS = [
|
|
168
164
|
'status',
|
|
169
165
|
'setup',
|
|
170
|
-
'init',
|
|
171
166
|
'doctor',
|
|
172
|
-
'review',
|
|
173
167
|
'agents',
|
|
174
168
|
'finish',
|
|
175
169
|
'report',
|
|
176
|
-
'copy-prompt',
|
|
177
|
-
'copy-commands',
|
|
178
170
|
'protect',
|
|
179
171
|
'sync',
|
|
180
172
|
'cleanup',
|
|
181
|
-
'
|
|
173
|
+
'prompt',
|
|
174
|
+
'help',
|
|
175
|
+
'version',
|
|
176
|
+
// deprecated aliases still routable with a warning
|
|
177
|
+
'init',
|
|
182
178
|
'install',
|
|
183
179
|
'fix',
|
|
184
180
|
'scan',
|
|
181
|
+
'review',
|
|
182
|
+
'copy-prompt',
|
|
183
|
+
'copy-commands',
|
|
185
184
|
'print-agents-snippet',
|
|
186
|
-
'
|
|
187
|
-
'version',
|
|
185
|
+
'release',
|
|
188
186
|
];
|
|
189
187
|
const CLI_COMMAND_DESCRIPTIONS = [
|
|
190
188
|
['status', 'Show GuardeX CLI + service health without modifying files'],
|
|
191
|
-
['setup', 'Install
|
|
192
|
-
['
|
|
193
|
-
['doctor', 'Repair safety setup drift, then verify repo safety'],
|
|
194
|
-
['report', 'Generate security/safety reports (for example: OpenSSF scorecard)'],
|
|
195
|
-
['finish', 'Auto-commit completed agent branches, then run PR finish flow'],
|
|
196
|
-
['copy-prompt', 'Print the AI-ready setup checklist'],
|
|
197
|
-
['copy-commands', 'Print setup checklist as executable commands only'],
|
|
189
|
+
['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target)'],
|
|
190
|
+
['doctor', 'Repair drift + verify (auto-sandboxes on protected main)'],
|
|
198
191
|
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
|
|
199
|
-
['sync', '
|
|
200
|
-
['
|
|
192
|
+
['sync', 'Sync agent branches with origin/<base>'],
|
|
193
|
+
['finish', 'Commit + PR + merge completed agent branches (--all, --branch)'],
|
|
194
|
+
['cleanup', 'Prune merged/stale agent branches and worktrees'],
|
|
201
195
|
['agents', 'Start/stop repo-scoped review + cleanup bots'],
|
|
202
|
-
['
|
|
203
|
-
['
|
|
204
|
-
['scan', 'Report safety issues and exit non-zero on findings'],
|
|
205
|
-
['print-agents-snippet', 'Print the AGENTS.md snippet template'],
|
|
206
|
-
['release', 'Publish GuardeX from maintainer release repo'],
|
|
196
|
+
['prompt', 'Print AI setup checklist (--exec, --snippet)'],
|
|
197
|
+
['report', 'Security/safety reports (e.g. OpenSSF scorecard)'],
|
|
207
198
|
['help', 'Show this help output'],
|
|
208
199
|
['version', 'Print GuardeX version'],
|
|
209
200
|
];
|
|
210
|
-
const
|
|
211
|
-
'setup',
|
|
212
|
-
'
|
|
213
|
-
'
|
|
214
|
-
'
|
|
215
|
-
'
|
|
216
|
-
'
|
|
217
|
-
'
|
|
201
|
+
const DEPRECATED_COMMAND_ALIASES = new Map([
|
|
202
|
+
['init', { target: 'setup', hint: 'gx setup' }],
|
|
203
|
+
['install', { target: 'setup', hint: 'gx setup --install-only' }],
|
|
204
|
+
['fix', { target: 'setup', hint: 'gx setup --repair' }],
|
|
205
|
+
['scan', { target: 'status', hint: 'gx status --strict' }],
|
|
206
|
+
['copy-prompt', { target: 'prompt', hint: 'gx prompt' }],
|
|
207
|
+
['copy-commands', { target: 'prompt', hint: 'gx prompt --exec' }],
|
|
208
|
+
['print-agents-snippet', { target: 'prompt', hint: 'gx prompt --snippet' }],
|
|
209
|
+
['review', { target: 'agents', hint: 'gx agents start (runs review + cleanup)' }],
|
|
218
210
|
]);
|
|
219
211
|
const AGENT_BOT_DESCRIPTIONS = [
|
|
220
|
-
['
|
|
221
|
-
['agents', 'Start/stop both review and cleanup bots for this repo'],
|
|
212
|
+
['agents', 'Start/stop review + cleanup bots for this repo'],
|
|
222
213
|
];
|
|
223
214
|
|
|
224
|
-
const AI_SETUP_PROMPT = `
|
|
225
|
-
|
|
226
|
-
1) Install
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
4) Optional: start continuous PR monitor from this repo:
|
|
244
|
-
gx review --interval 30
|
|
245
|
-
|
|
246
|
-
5) Confirm next safe agent workflow commands:
|
|
247
|
-
bash scripts/codex-agent.sh "task" "agent-name"
|
|
248
|
-
bash scripts/agent-branch-start.sh "task" "agent-name"
|
|
249
|
-
python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
|
|
250
|
-
bash scripts/agent-branch-finish.sh --branch "$(git rev-parse --abbrev-ref HEAD)" --base dev --via-pr --wait-for-merge
|
|
251
|
-
- For every new user message/task, repeat the same cycle:
|
|
252
|
-
start isolated agent branch/worktree -> claim file locks -> implement/verify ->
|
|
253
|
-
finish via PR/merge cleanup into dev with scripts/agent-branch-finish.sh.
|
|
254
|
-
- Finished branches stay available by default for audit/follow-up.
|
|
255
|
-
Remove them explicitly when done:
|
|
256
|
-
gx cleanup --branch "$(git rev-parse --abbrev-ref HEAD)"
|
|
257
|
-
- To finalize all completed agent branches in one pass:
|
|
258
|
-
gx finish --all
|
|
259
|
-
|
|
260
|
-
6) OpenSpec default change flow (core profile):
|
|
261
|
-
/opsx:propose <change-name>
|
|
262
|
-
/opsx:apply
|
|
263
|
-
/opsx:archive
|
|
264
|
-
- Full guide: docs/openspec-getting-started.md
|
|
265
|
-
|
|
266
|
-
7) Optional: enable expanded OpenSpec workflow commands:
|
|
267
|
-
openspec config profile <profile-name>
|
|
268
|
-
openspec update
|
|
269
|
-
- Expanded path: /opsx:new -> /opsx:ff or /opsx:continue -> /opsx:apply -> /opsx:verify -> /opsx:archive
|
|
270
|
-
|
|
271
|
-
8) Optional: create OpenSpec planning workspace:
|
|
272
|
-
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
|
|
273
|
-
|
|
274
|
-
9) Optional: protect extra branches:
|
|
275
|
-
gx protect add release staging
|
|
276
|
-
|
|
277
|
-
10) Optional: sync your current agent branch with latest base branch:
|
|
278
|
-
gx sync --check
|
|
279
|
-
gx sync
|
|
280
|
-
|
|
281
|
-
11) Optional (GitHub remote cleanup): enable:
|
|
282
|
-
Settings -> General -> Pull Requests -> Automatically delete head branches
|
|
283
|
-
|
|
284
|
-
12) Optional (fork sync with Pull app):
|
|
285
|
-
cp .github/pull.yml.example .github/pull.yml
|
|
286
|
-
# then edit .github/pull.yml:
|
|
287
|
-
# - set rules[].base to your fork branch (main/master/dev)
|
|
288
|
-
# - set rules[].upstream to upstream-owner:branch
|
|
289
|
-
# install app: https://github.com/apps/pull
|
|
290
|
-
# validate config: https://pull.git.ci/check/<owner>/<repo>
|
|
291
|
-
|
|
292
|
-
13) Optional (PR review bot with cr-gpt GitHub App):
|
|
293
|
-
- install app: https://github.com/apps/cr-gpt
|
|
294
|
-
- in GitHub repo Settings -> Secrets and variables -> Actions -> Variables:
|
|
295
|
-
add OPENAI_API_KEY (your API key)
|
|
296
|
-
- the app reviews new/updated pull requests automatically
|
|
297
|
-
|
|
298
|
-
14) Optional: test PR review action workflow
|
|
299
|
-
- gx setup installs .github/workflows/cr.yml
|
|
300
|
-
- open or update a PR
|
|
301
|
-
- check Actions -> "Code Review" run logs + PR timeline comments
|
|
215
|
+
const AI_SETUP_PROMPT = `GuardeX (gx) setup checklist for Codex/Claude in this repo.
|
|
216
|
+
|
|
217
|
+
1) Install: npm i -g @imdeadpool/guardex && gh --version
|
|
218
|
+
2) Bootstrap: gx setup # installs hooks/templates + verifies; prompts Y/N for global OMX/OpenSpec/codex-auth
|
|
219
|
+
3) If degraded: gx doctor # repair + re-verify
|
|
220
|
+
4) Per task: bash scripts/codex-agent.sh "<task>" "<agent>"
|
|
221
|
+
# or manual:
|
|
222
|
+
# bash scripts/agent-branch-start.sh "<task>" "<agent>"
|
|
223
|
+
# python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
|
|
224
|
+
# bash scripts/agent-branch-finish.sh --branch "$(git rev-parse --abbrev-ref HEAD)" --via-pr --wait-for-merge
|
|
225
|
+
5) Finalize all: gx finish --all
|
|
226
|
+
6) Cleanup: gx cleanup
|
|
227
|
+
7) OpenSpec: /opsx:propose -> /opsx:apply -> /opsx:archive (see docs/openspec-getting-started.md)
|
|
228
|
+
8) Protect: gx protect add release staging (optional)
|
|
229
|
+
9) Sync: gx sync --check && gx sync (optional; rebase onto base)
|
|
230
|
+
10) Fork sync: cp .github/pull.yml.example .github/pull.yml (optional; install https://github.com/apps/pull)
|
|
231
|
+
11) PR review bot: install https://github.com/apps/cr-gpt + set OPENAI_API_KEY in Actions variables (uses .github/workflows/cr.yml)
|
|
232
|
+
12) GitHub repo: enable Settings -> PRs -> Automatically delete head branches
|
|
302
233
|
`;
|
|
303
234
|
|
|
304
235
|
const AI_SETUP_COMMANDS = `npm i -g @imdeadpool/guardex
|
|
305
236
|
gh --version
|
|
306
237
|
gx setup
|
|
307
238
|
gx doctor
|
|
308
|
-
|
|
309
|
-
bash scripts/codex-agent.sh "task" "agent-name"
|
|
310
|
-
bash scripts/agent-branch-start.sh "task" "agent-name"
|
|
311
|
-
python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
|
|
312
|
-
bash scripts/agent-branch-finish.sh --branch "$(git rev-parse --abbrev-ref HEAD)" --base dev --via-pr --wait-for-merge
|
|
239
|
+
bash scripts/codex-agent.sh "<task>" "<agent>"
|
|
313
240
|
gx finish --all
|
|
314
|
-
gx cleanup
|
|
315
|
-
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
|
|
316
|
-
openspec config profile <profile-name>
|
|
317
|
-
openspec update
|
|
241
|
+
gx cleanup
|
|
318
242
|
gx protect add release staging
|
|
319
|
-
gx sync --check
|
|
320
243
|
gx sync
|
|
321
|
-
cp .github/pull.yml.example .github/pull.yml
|
|
322
244
|
`;
|
|
323
245
|
|
|
324
246
|
const SCORECARD_RISK_BY_CHECK = {
|
|
@@ -364,15 +286,12 @@ function statusDot(status) {
|
|
|
364
286
|
return colorize('●', '33'); // yellow for degraded/unknown
|
|
365
287
|
}
|
|
366
288
|
|
|
367
|
-
function commandCatalogLines(indent = ' '
|
|
368
|
-
const
|
|
369
|
-
? CLI_COMMAND_DESCRIPTIONS.filter(([name]) => CORE_COMMAND_NAMES.has(name))
|
|
370
|
-
: CLI_COMMAND_DESCRIPTIONS;
|
|
371
|
-
const maxCommandLength = entries.reduce(
|
|
289
|
+
function commandCatalogLines(indent = ' ') {
|
|
290
|
+
const maxCommandLength = CLI_COMMAND_DESCRIPTIONS.reduce(
|
|
372
291
|
(max, [command]) => Math.max(max, command.length),
|
|
373
292
|
0,
|
|
374
293
|
);
|
|
375
|
-
return
|
|
294
|
+
return CLI_COMMAND_DESCRIPTIONS.map(
|
|
376
295
|
([command, description]) => `${indent}${command.padEnd(maxCommandLength + 2)}${description}`,
|
|
377
296
|
);
|
|
378
297
|
}
|
|
@@ -389,7 +308,7 @@ function agentBotCatalogLines(indent = ' ') {
|
|
|
389
308
|
|
|
390
309
|
function printToolLogsSummary() {
|
|
391
310
|
const usageLine = ` $ ${SHORT_TOOL_NAME} <command> [options]`;
|
|
392
|
-
const commandDetails = commandCatalogLines(' '
|
|
311
|
+
const commandDetails = commandCatalogLines(' ');
|
|
393
312
|
const agentBotDetails = agentBotCatalogLines(' ');
|
|
394
313
|
|
|
395
314
|
if (!supportsAnsiColors()) {
|
|
@@ -434,7 +353,7 @@ function printToolLogsSummary() {
|
|
|
434
353
|
}
|
|
435
354
|
console.log(` ${pipe}${line.slice(2)}`);
|
|
436
355
|
}
|
|
437
|
-
console.log(` ${corner}─ ${colorize(`Try '${
|
|
356
|
+
console.log(` ${corner}─ ${colorize(`Try '${TOOL_NAME} doctor' for one-step repair + verification.`, '2')}`);
|
|
438
357
|
}
|
|
439
358
|
|
|
440
359
|
function usage(options = {}) {
|
|
@@ -455,19 +374,12 @@ AGENT BOT
|
|
|
455
374
|
${agentBotCatalogLines().join('\n')}
|
|
456
375
|
|
|
457
376
|
NOTES
|
|
458
|
-
-
|
|
459
|
-
-
|
|
460
|
-
-
|
|
461
|
-
-
|
|
462
|
-
- ${
|
|
463
|
-
-
|
|
464
|
-
- Optional parent-folder Source Control view: ${SHORT_TOOL_NAME} setup --target <repo-path> --parent-workspace-view
|
|
465
|
-
- In initialized repos, setup/install/fix block in-place writes on protected main by default
|
|
466
|
-
- setup/doctor auto-finish clean pending agent/* branches via PR flow into the current local base branch
|
|
467
|
-
- doctor auto-runs in a sandbox agent branch/worktree on protected main and tries auto-finish PR flow
|
|
468
|
-
- agent-branch-finish merges by default and keeps agent branches/worktrees until explicit cleanup
|
|
469
|
-
- use '${SHORT_TOOL_NAME} cleanup' to remove merged agent branches/worktrees (optionally remote refs too)
|
|
470
|
-
- Legacy command aliases are still supported: ${LEGACY_NAMES.join(', ')}`);
|
|
377
|
+
- No command = ${SHORT_TOOL_NAME} status. ${SHORT_TOOL_NAME} init is an alias of ${SHORT_TOOL_NAME} setup.
|
|
378
|
+
- Global installs need Y/N approval; GitHub CLI (gh) is required for PR automation.
|
|
379
|
+
- Target another repo: ${SHORT_TOOL_NAME} <cmd> --target <repo-path>.
|
|
380
|
+
- On protected main, setup/install/fix/doctor auto-sandbox via agent branch + PR flow.
|
|
381
|
+
- Run '${SHORT_TOOL_NAME} cleanup' to prune merged agent branches/worktrees.
|
|
382
|
+
- Legacy aliases: ${LEGACY_NAMES.join(', ')}.`);
|
|
471
383
|
|
|
472
384
|
if (outsideGitRepo) {
|
|
473
385
|
console.log(`
|
|
@@ -513,6 +425,90 @@ function isGitRepo(targetPath) {
|
|
|
513
425
|
return result.status === 0;
|
|
514
426
|
}
|
|
515
427
|
|
|
428
|
+
const NESTED_REPO_DEFAULT_MAX_DEPTH = 6;
|
|
429
|
+
const NESTED_REPO_DEFAULT_SKIP_DIRS = new Set([
|
|
430
|
+
'node_modules',
|
|
431
|
+
'.git',
|
|
432
|
+
'dist',
|
|
433
|
+
'build',
|
|
434
|
+
'.next',
|
|
435
|
+
'.cache',
|
|
436
|
+
'target',
|
|
437
|
+
'vendor',
|
|
438
|
+
'.venv',
|
|
439
|
+
'.pnpm-store',
|
|
440
|
+
]);
|
|
441
|
+
const NESTED_REPO_WORKTREE_RELATIVE_DIR = path.join('.omx', 'agent-worktrees');
|
|
442
|
+
|
|
443
|
+
function discoverNestedGitRepos(rootPath, opts = {}) {
|
|
444
|
+
const maxDepth = Number.isFinite(opts.maxDepth) ? Math.max(1, opts.maxDepth) : NESTED_REPO_DEFAULT_MAX_DEPTH;
|
|
445
|
+
const extraSkip = new Set(Array.isArray(opts.extraSkip) ? opts.extraSkip : []);
|
|
446
|
+
const includeSubmodules = Boolean(opts.includeSubmodules);
|
|
447
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
448
|
+
|
|
449
|
+
const rootCommonDir = (() => {
|
|
450
|
+
const result = run('git', ['-C', resolvedRoot, 'rev-parse', '--git-common-dir'], { cwd: resolvedRoot });
|
|
451
|
+
if (result.status !== 0) return null;
|
|
452
|
+
const raw = result.stdout.trim();
|
|
453
|
+
if (!raw) return null;
|
|
454
|
+
return path.resolve(resolvedRoot, raw);
|
|
455
|
+
})();
|
|
456
|
+
|
|
457
|
+
const workreeSkipAbsolute = path.join(resolvedRoot, NESTED_REPO_WORKTREE_RELATIVE_DIR);
|
|
458
|
+
const found = new Set();
|
|
459
|
+
found.add(resolvedRoot);
|
|
460
|
+
|
|
461
|
+
function shouldSkipDir(dirName) {
|
|
462
|
+
return NESTED_REPO_DEFAULT_SKIP_DIRS.has(dirName) || extraSkip.has(dirName);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function walk(currentPath, depth) {
|
|
466
|
+
if (depth > maxDepth) return;
|
|
467
|
+
let entries;
|
|
468
|
+
try {
|
|
469
|
+
entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
470
|
+
} catch {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
for (const entry of entries) {
|
|
475
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
476
|
+
|
|
477
|
+
if (entry.name === '.git') {
|
|
478
|
+
if (entry.isDirectory()) {
|
|
479
|
+
if (entryPath === path.join(resolvedRoot, '.git')) continue;
|
|
480
|
+
found.add(path.dirname(entryPath));
|
|
481
|
+
} else if (includeSubmodules && entry.isFile()) {
|
|
482
|
+
found.add(path.dirname(entryPath));
|
|
483
|
+
}
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
|
|
488
|
+
if (shouldSkipDir(entry.name)) continue;
|
|
489
|
+
if (entryPath === workreeSkipAbsolute) continue;
|
|
490
|
+
walk(entryPath, depth + 1);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
walk(resolvedRoot, 0);
|
|
495
|
+
|
|
496
|
+
const filtered = Array.from(found).filter((repoPath) => {
|
|
497
|
+
if (repoPath === resolvedRoot) return true;
|
|
498
|
+
if (!rootCommonDir) return true;
|
|
499
|
+
const childResult = run('git', ['-C', repoPath, 'rev-parse', '--git-common-dir'], { cwd: repoPath });
|
|
500
|
+
if (childResult.status !== 0) return true;
|
|
501
|
+
const childCommonDirRaw = childResult.stdout.trim();
|
|
502
|
+
if (!childCommonDirRaw) return true;
|
|
503
|
+
const childCommonDir = path.resolve(repoPath, childCommonDirRaw);
|
|
504
|
+
return childCommonDir !== rootCommonDir;
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const [root, ...rest] = filtered;
|
|
508
|
+
rest.sort((a, b) => a.localeCompare(b));
|
|
509
|
+
return [root, ...rest];
|
|
510
|
+
}
|
|
511
|
+
|
|
516
512
|
function toDestinationPath(relativeTemplatePath) {
|
|
517
513
|
if (relativeTemplatePath.startsWith('scripts/')) {
|
|
518
514
|
return relativeTemplatePath;
|
|
@@ -709,7 +705,8 @@ function writeLockState(repoRoot, payload, dryRun) {
|
|
|
709
705
|
fs.writeFileSync(lockPath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
710
706
|
}
|
|
711
707
|
|
|
712
|
-
function ensurePackageScripts(repoRoot, dryRun) {
|
|
708
|
+
function ensurePackageScripts(repoRoot, dryRun, options = {}) {
|
|
709
|
+
const force = Boolean(options.force);
|
|
713
710
|
const packagePath = path.join(repoRoot, 'package.json');
|
|
714
711
|
if (!fs.existsSync(packagePath)) {
|
|
715
712
|
return { status: 'skipped', file: 'package.json', note: 'package.json not found' };
|
|
@@ -722,30 +719,15 @@ function ensurePackageScripts(repoRoot, dryRun) {
|
|
|
722
719
|
throw new Error(`Unable to parse package.json in target repo: ${error.message}`);
|
|
723
720
|
}
|
|
724
721
|
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
'
|
|
731
|
-
|
|
732
|
-
'agent:hooks:install': 'bash ./scripts/install-agent-git-hooks.sh',
|
|
733
|
-
'agent:locks:claim': 'python3 ./scripts/agent-file-locks.py claim',
|
|
734
|
-
'agent:locks:allow-delete': 'python3 ./scripts/agent-file-locks.py allow-delete',
|
|
735
|
-
'agent:locks:release': 'python3 ./scripts/agent-file-locks.py release',
|
|
736
|
-
'agent:locks:status': 'python3 ./scripts/agent-file-locks.py status',
|
|
737
|
-
'agent:plan:init': 'bash ./scripts/openspec/init-plan-workspace.sh',
|
|
738
|
-
'agent:change:init': 'bash ./scripts/openspec/init-change-workspace.sh',
|
|
739
|
-
'agent:protect:list': `${SHORT_TOOL_NAME} protect list`,
|
|
740
|
-
'agent:branch:sync': `${SHORT_TOOL_NAME} sync`,
|
|
741
|
-
'agent:branch:sync:check': `${SHORT_TOOL_NAME} sync --check`,
|
|
742
|
-
'agent:safety:setup': `${SHORT_TOOL_NAME} setup`,
|
|
743
|
-
'agent:safety:scan': `${SHORT_TOOL_NAME} scan`,
|
|
744
|
-
'agent:safety:fix': `${SHORT_TOOL_NAME} fix`,
|
|
745
|
-
'agent:safety:doctor': `${SHORT_TOOL_NAME} doctor`,
|
|
746
|
-
};
|
|
722
|
+
const existingScripts = pkg.scripts && typeof pkg.scripts === 'object'
|
|
723
|
+
? pkg.scripts
|
|
724
|
+
: {};
|
|
725
|
+
const hasExistingAgentScripts = Object.keys(existingScripts).some((key) => key.startsWith('agent:'));
|
|
726
|
+
if (hasExistingAgentScripts && !force) {
|
|
727
|
+
return { status: 'unchanged', file: 'package.json', note: 'preserved existing agent:* scripts' };
|
|
728
|
+
}
|
|
747
729
|
|
|
748
|
-
pkg.scripts =
|
|
730
|
+
pkg.scripts = existingScripts;
|
|
749
731
|
let changed = false;
|
|
750
732
|
for (const [key, value] of Object.entries(REQUIRED_PACKAGE_SCRIPTS)) {
|
|
751
733
|
if (pkg.scripts[key] !== value) {
|
|
@@ -765,7 +747,8 @@ function ensurePackageScripts(repoRoot, dryRun) {
|
|
|
765
747
|
return { status: 'updated', file: 'package.json' };
|
|
766
748
|
}
|
|
767
749
|
|
|
768
|
-
function ensureAgentsSnippet(repoRoot, dryRun) {
|
|
750
|
+
function ensureAgentsSnippet(repoRoot, dryRun, options = {}) {
|
|
751
|
+
const force = Boolean(options.force);
|
|
769
752
|
const agentsPath = path.join(repoRoot, 'AGENTS.md');
|
|
770
753
|
const snippet = fs.readFileSync(path.join(TEMPLATE_ROOT, 'AGENTS.multiagent-safety.md'), 'utf8').trimEnd();
|
|
771
754
|
const managedRegex = new RegExp(
|
|
@@ -782,6 +765,9 @@ function ensureAgentsSnippet(repoRoot, dryRun) {
|
|
|
782
765
|
|
|
783
766
|
const existing = fs.readFileSync(agentsPath, 'utf8');
|
|
784
767
|
if (managedRegex.test(existing)) {
|
|
768
|
+
if (!force) {
|
|
769
|
+
return { status: 'unchanged', file: 'AGENTS.md', note: 'preserved existing guardex-managed block' };
|
|
770
|
+
}
|
|
785
771
|
const next = existing.replace(managedRegex, snippet);
|
|
786
772
|
if (next === existing) {
|
|
787
773
|
return { status: 'unchanged', file: 'AGENTS.md' };
|
|
@@ -925,10 +911,18 @@ function parseCommonArgs(rawArgs, defaults) {
|
|
|
925
911
|
}
|
|
926
912
|
|
|
927
913
|
function parseSetupArgs(rawArgs, defaults) {
|
|
928
|
-
const setupDefaults = {
|
|
914
|
+
const setupDefaults = {
|
|
915
|
+
...defaults,
|
|
916
|
+
parentWorkspaceView: false,
|
|
917
|
+
recursive: true,
|
|
918
|
+
nestedMaxDepth: NESTED_REPO_DEFAULT_MAX_DEPTH,
|
|
919
|
+
nestedSkipDirs: [],
|
|
920
|
+
includeSubmodules: false,
|
|
921
|
+
};
|
|
929
922
|
const forwardedArgs = [];
|
|
930
923
|
|
|
931
|
-
for (
|
|
924
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
925
|
+
const arg = rawArgs[index];
|
|
932
926
|
if (arg === '--parent-workspace-view') {
|
|
933
927
|
setupDefaults.parentWorkspaceView = true;
|
|
934
928
|
continue;
|
|
@@ -937,6 +931,34 @@ function parseSetupArgs(rawArgs, defaults) {
|
|
|
937
931
|
setupDefaults.parentWorkspaceView = false;
|
|
938
932
|
continue;
|
|
939
933
|
}
|
|
934
|
+
if (arg === '--no-recursive' || arg === '--no-nested' || arg === '--single-repo') {
|
|
935
|
+
setupDefaults.recursive = false;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
if (arg === '--recursive' || arg === '--nested') {
|
|
939
|
+
setupDefaults.recursive = true;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (arg === '--max-depth') {
|
|
943
|
+
const raw = requireValue(rawArgs, index, '--max-depth');
|
|
944
|
+
const parsed = Number.parseInt(raw, 10);
|
|
945
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
946
|
+
throw new Error('--max-depth requires a positive integer');
|
|
947
|
+
}
|
|
948
|
+
setupDefaults.nestedMaxDepth = parsed;
|
|
949
|
+
index += 1;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (arg === '--skip-nested') {
|
|
953
|
+
const raw = requireValue(rawArgs, index, '--skip-nested');
|
|
954
|
+
setupDefaults.nestedSkipDirs.push(raw);
|
|
955
|
+
index += 1;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
if (arg === '--include-submodules') {
|
|
959
|
+
setupDefaults.includeSubmodules = true;
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
940
962
|
forwardedArgs.push(arg);
|
|
941
963
|
}
|
|
942
964
|
|
|
@@ -1092,6 +1114,7 @@ function resolveSandboxTarget(repoRoot, worktreePath, targetPath) {
|
|
|
1092
1114
|
function buildSandboxDoctorArgs(options, sandboxTarget) {
|
|
1093
1115
|
const args = ['doctor', '--target', sandboxTarget];
|
|
1094
1116
|
if (options.dryRun) args.push('--dry-run');
|
|
1117
|
+
if (options.force) args.push('--force');
|
|
1095
1118
|
if (options.skipAgents) args.push('--skip-agents');
|
|
1096
1119
|
if (options.skipPackageJson) args.push('--skip-package-json');
|
|
1097
1120
|
if (options.skipGitignore) args.push('--no-gitignore');
|
|
@@ -3528,11 +3551,11 @@ function runInstallInternal(options) {
|
|
|
3528
3551
|
}
|
|
3529
3552
|
|
|
3530
3553
|
if (!options.skipPackageJson) {
|
|
3531
|
-
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun)));
|
|
3554
|
+
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3532
3555
|
}
|
|
3533
3556
|
|
|
3534
3557
|
if (!options.skipAgents) {
|
|
3535
|
-
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun)));
|
|
3558
|
+
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3536
3559
|
}
|
|
3537
3560
|
|
|
3538
3561
|
const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
|
|
@@ -3582,11 +3605,11 @@ function runFixInternal(options) {
|
|
|
3582
3605
|
}
|
|
3583
3606
|
|
|
3584
3607
|
if (!options.skipPackageJson) {
|
|
3585
|
-
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun)));
|
|
3608
|
+
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3586
3609
|
}
|
|
3587
3610
|
|
|
3588
3611
|
if (!options.skipAgents) {
|
|
3589
|
-
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun)));
|
|
3612
|
+
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3590
3613
|
}
|
|
3591
3614
|
|
|
3592
3615
|
const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
|
|
@@ -4453,23 +4476,87 @@ function setup(rawArgs) {
|
|
|
4453
4476
|
}
|
|
4454
4477
|
}
|
|
4455
4478
|
|
|
4456
|
-
|
|
4457
|
-
const
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4479
|
+
const topRepoRoot = resolveRepoRoot(options.target);
|
|
4480
|
+
const discoveredRepos = options.recursive
|
|
4481
|
+
? discoverNestedGitRepos(topRepoRoot, {
|
|
4482
|
+
maxDepth: options.nestedMaxDepth,
|
|
4483
|
+
extraSkip: options.nestedSkipDirs,
|
|
4484
|
+
includeSubmodules: options.includeSubmodules,
|
|
4485
|
+
})
|
|
4486
|
+
: [topRepoRoot];
|
|
4487
|
+
|
|
4488
|
+
if (discoveredRepos.length > 1) {
|
|
4489
|
+
console.log(
|
|
4490
|
+
`[${TOOL_NAME}] Detected ${discoveredRepos.length} git repos under ${topRepoRoot}. Installing into each (use --no-recursive to limit to the top-level).`,
|
|
4491
|
+
);
|
|
4492
|
+
for (const repoPath of discoveredRepos) {
|
|
4493
|
+
const marker = repoPath === topRepoRoot ? ' (top-level)' : '';
|
|
4494
|
+
console.log(`[${TOOL_NAME}] - ${repoPath}${marker}`);
|
|
4495
|
+
}
|
|
4461
4496
|
}
|
|
4462
|
-
printOperations('Setup/install', installPayload, options.dryRun);
|
|
4463
4497
|
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4498
|
+
let aggregateErrors = 0;
|
|
4499
|
+
let aggregateWarnings = 0;
|
|
4500
|
+
let lastScanResult = null;
|
|
4501
|
+
|
|
4502
|
+
for (const repoPath of discoveredRepos) {
|
|
4503
|
+
const perRepoOptions = { ...options, target: repoPath };
|
|
4504
|
+
const repoLabel = discoveredRepos.length > 1 ? ` [${path.relative(topRepoRoot, repoPath) || '.'}]` : '';
|
|
4505
|
+
|
|
4506
|
+
if (discoveredRepos.length > 1) {
|
|
4507
|
+
console.log(`[${TOOL_NAME}] ── Setup target: ${repoPath} ──`);
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
assertProtectedMainWriteAllowed(perRepoOptions, 'setup');
|
|
4511
|
+
const installPayload = runInstallInternal(perRepoOptions);
|
|
4512
|
+
installPayload.operations.push(ensureSetupProtectedBranches(installPayload.repoRoot, Boolean(perRepoOptions.dryRun)));
|
|
4513
|
+
if (perRepoOptions.parentWorkspaceView) {
|
|
4514
|
+
installPayload.operations.push(ensureParentWorkspaceView(installPayload.repoRoot, Boolean(perRepoOptions.dryRun)));
|
|
4515
|
+
}
|
|
4516
|
+
printOperations(`Setup/install${repoLabel}`, installPayload, perRepoOptions.dryRun);
|
|
4517
|
+
|
|
4518
|
+
const fixPayload = runFixInternal({
|
|
4519
|
+
target: repoPath,
|
|
4520
|
+
dryRun: perRepoOptions.dryRun,
|
|
4521
|
+
force: perRepoOptions.force,
|
|
4522
|
+
dropStaleLocks: true,
|
|
4523
|
+
skipAgents: perRepoOptions.skipAgents,
|
|
4524
|
+
skipPackageJson: perRepoOptions.skipPackageJson,
|
|
4525
|
+
skipGitignore: perRepoOptions.skipGitignore,
|
|
4526
|
+
});
|
|
4527
|
+
printOperations(`Setup/fix${repoLabel}`, fixPayload, perRepoOptions.dryRun);
|
|
4528
|
+
|
|
4529
|
+
if (perRepoOptions.dryRun) {
|
|
4530
|
+
continue;
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
if (perRepoOptions.parentWorkspaceView) {
|
|
4534
|
+
const parentWorkspace = buildParentWorkspaceView(installPayload.repoRoot);
|
|
4535
|
+
console.log(`[${TOOL_NAME}] Parent workspace view: ${parentWorkspace.workspacePath}`);
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4538
|
+
const scanResult = runScanInternal({ target: repoPath, json: false });
|
|
4539
|
+
const currentBaseBranch = currentBranchName(scanResult.repoRoot);
|
|
4540
|
+
const autoFinishSummary = autoFinishReadyAgentBranches(scanResult.repoRoot, {
|
|
4541
|
+
baseBranch: currentBaseBranch,
|
|
4542
|
+
dryRun: perRepoOptions.dryRun,
|
|
4543
|
+
});
|
|
4544
|
+
printScanResult(scanResult, false);
|
|
4545
|
+
if (autoFinishSummary.enabled) {
|
|
4546
|
+
console.log(
|
|
4547
|
+
`[${TOOL_NAME}] Auto-finish sweep (base=${currentBaseBranch}): attempted=${autoFinishSummary.attempted}, completed=${autoFinishSummary.completed}, skipped=${autoFinishSummary.skipped}, failed=${autoFinishSummary.failed}`,
|
|
4548
|
+
);
|
|
4549
|
+
for (const detail of autoFinishSummary.details) {
|
|
4550
|
+
console.log(`[${TOOL_NAME}] ${detail}`);
|
|
4551
|
+
}
|
|
4552
|
+
} else if (autoFinishSummary.details.length > 0) {
|
|
4553
|
+
console.log(`[${TOOL_NAME}] ${autoFinishSummary.details[0]}`);
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4556
|
+
aggregateErrors += scanResult.errors;
|
|
4557
|
+
aggregateWarnings += scanResult.warnings;
|
|
4558
|
+
lastScanResult = scanResult;
|
|
4559
|
+
}
|
|
4473
4560
|
|
|
4474
4561
|
if (options.dryRun) {
|
|
4475
4562
|
console.log(`[${TOOL_NAME}] Dry run setup done.`);
|
|
@@ -4477,32 +4564,11 @@ function setup(rawArgs) {
|
|
|
4477
4564
|
return;
|
|
4478
4565
|
}
|
|
4479
4566
|
|
|
4480
|
-
if (
|
|
4481
|
-
const
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
const scanResult = runScanInternal({ target: options.target, json: false });
|
|
4486
|
-
const currentBaseBranch = currentBranchName(scanResult.repoRoot);
|
|
4487
|
-
const autoFinishSummary = autoFinishReadyAgentBranches(scanResult.repoRoot, {
|
|
4488
|
-
baseBranch: currentBaseBranch,
|
|
4489
|
-
dryRun: options.dryRun,
|
|
4490
|
-
});
|
|
4491
|
-
printScanResult(scanResult, false);
|
|
4492
|
-
if (autoFinishSummary.enabled) {
|
|
4493
|
-
console.log(
|
|
4494
|
-
`[${TOOL_NAME}] Auto-finish sweep (base=${currentBaseBranch}): attempted=${autoFinishSummary.attempted}, completed=${autoFinishSummary.completed}, skipped=${autoFinishSummary.skipped}, failed=${autoFinishSummary.failed}`,
|
|
4495
|
-
);
|
|
4496
|
-
for (const detail of autoFinishSummary.details) {
|
|
4497
|
-
console.log(`[${TOOL_NAME}] ${detail}`);
|
|
4498
|
-
}
|
|
4499
|
-
} else if (autoFinishSummary.details.length > 0) {
|
|
4500
|
-
console.log(`[${TOOL_NAME}] ${autoFinishSummary.details[0]}`);
|
|
4501
|
-
}
|
|
4502
|
-
|
|
4503
|
-
if (scanResult.errors === 0 && scanResult.warnings === 0) {
|
|
4504
|
-
console.log(`[${TOOL_NAME}] ✅ Setup complete.`);
|
|
4505
|
-
console.log(`[${TOOL_NAME}] Copy AI setup prompt with: ${SHORT_TOOL_NAME} copy-prompt`);
|
|
4567
|
+
if (aggregateErrors === 0 && aggregateWarnings === 0) {
|
|
4568
|
+
const repoCount = discoveredRepos.length;
|
|
4569
|
+
const suffix = repoCount > 1 ? ` (${repoCount} repos)` : '';
|
|
4570
|
+
console.log(`[${TOOL_NAME}] ✅ Setup complete.${suffix}`);
|
|
4571
|
+
console.log(`[${TOOL_NAME}] Copy AI setup prompt with: ${SHORT_TOOL_NAME} prompt`);
|
|
4506
4572
|
console.log(
|
|
4507
4573
|
`[${TOOL_NAME}] OpenSpec core workflow: /opsx:propose -> /opsx:apply -> /opsx:archive`,
|
|
4508
4574
|
);
|
|
@@ -4512,7 +4578,13 @@ function setup(rawArgs) {
|
|
|
4512
4578
|
console.log(`[${TOOL_NAME}] OpenSpec guide: docs/openspec-getting-started.md`);
|
|
4513
4579
|
}
|
|
4514
4580
|
|
|
4515
|
-
|
|
4581
|
+
if (lastScanResult) {
|
|
4582
|
+
setExitCodeFromScan({
|
|
4583
|
+
...lastScanResult,
|
|
4584
|
+
errors: aggregateErrors,
|
|
4585
|
+
warnings: aggregateWarnings,
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
4516
4588
|
}
|
|
4517
4589
|
|
|
4518
4590
|
function ensureMainBranch(repoRoot) {
|
|
@@ -4811,6 +4883,31 @@ function copyCommands() {
|
|
|
4811
4883
|
process.exitCode = 0;
|
|
4812
4884
|
}
|
|
4813
4885
|
|
|
4886
|
+
function prompt(rawArgs) {
|
|
4887
|
+
const args = Array.isArray(rawArgs) ? rawArgs : [];
|
|
4888
|
+
let variant = 'prompt';
|
|
4889
|
+
for (const arg of args) {
|
|
4890
|
+
if (arg === '--exec' || arg === '--commands') variant = 'exec';
|
|
4891
|
+
else if (arg === '--snippet' || arg === '--agents') variant = 'snippet';
|
|
4892
|
+
else if (arg === '--prompt' || arg === '--full') variant = 'prompt';
|
|
4893
|
+
else if (arg === '-h' || arg === '--help') variant = 'help';
|
|
4894
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
4895
|
+
}
|
|
4896
|
+
if (variant === 'help') {
|
|
4897
|
+
console.log(
|
|
4898
|
+
`${SHORT_TOOL_NAME} prompt commands:\n` +
|
|
4899
|
+
` ${SHORT_TOOL_NAME} prompt Print AI setup checklist\n` +
|
|
4900
|
+
` ${SHORT_TOOL_NAME} prompt --exec Print setup commands only (shell-ready)\n` +
|
|
4901
|
+
` ${SHORT_TOOL_NAME} prompt --snippet Print the AGENTS.md managed-block template`,
|
|
4902
|
+
);
|
|
4903
|
+
process.exitCode = 0;
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
if (variant === 'exec') return copyCommands();
|
|
4907
|
+
if (variant === 'snippet') return printAgentsSnippet();
|
|
4908
|
+
return copyPrompt();
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4814
4911
|
function cleanup(rawArgs) {
|
|
4815
4912
|
const options = parseCleanupArgs(rawArgs);
|
|
4816
4913
|
const repoRoot = resolveRepoRoot(options.target);
|
|
@@ -5289,6 +5386,29 @@ function normalizeCommandOrThrow(command) {
|
|
|
5289
5386
|
return command;
|
|
5290
5387
|
}
|
|
5291
5388
|
|
|
5389
|
+
function warnDeprecatedAlias(aliasName) {
|
|
5390
|
+
const entry = DEPRECATED_COMMAND_ALIASES.get(aliasName);
|
|
5391
|
+
if (!entry) return;
|
|
5392
|
+
console.error(
|
|
5393
|
+
`[${TOOL_NAME}] '${aliasName}' is deprecated and will be removed in a future major release. ` +
|
|
5394
|
+
`Use: ${entry.hint}`,
|
|
5395
|
+
);
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5398
|
+
function extractFlag(args, ...names) {
|
|
5399
|
+
const flagSet = new Set(names);
|
|
5400
|
+
let found = false;
|
|
5401
|
+
const remaining = [];
|
|
5402
|
+
for (const arg of args) {
|
|
5403
|
+
if (flagSet.has(arg)) {
|
|
5404
|
+
found = true;
|
|
5405
|
+
} else {
|
|
5406
|
+
remaining.push(arg);
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
return { found, remaining };
|
|
5410
|
+
}
|
|
5411
|
+
|
|
5292
5412
|
function main() {
|
|
5293
5413
|
const args = process.argv.slice(2);
|
|
5294
5414
|
|
|
@@ -5312,90 +5432,42 @@ function main() {
|
|
|
5312
5432
|
return;
|
|
5313
5433
|
}
|
|
5314
5434
|
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
return;
|
|
5435
|
+
// Deprecated direct aliases — route to new surface and warn once.
|
|
5436
|
+
if (DEPRECATED_COMMAND_ALIASES.has(command)) {
|
|
5437
|
+
warnDeprecatedAlias(command);
|
|
5438
|
+
if (command === 'init') return setup(rest);
|
|
5439
|
+
if (command === 'install') return install(rest);
|
|
5440
|
+
if (command === 'fix') return fix(rest);
|
|
5441
|
+
if (command === 'scan') return scan(rest);
|
|
5442
|
+
if (command === 'copy-prompt') return copyPrompt();
|
|
5443
|
+
if (command === 'copy-commands') return copyCommands();
|
|
5444
|
+
if (command === 'print-agents-snippet') return printAgentsSnippet();
|
|
5445
|
+
if (command === 'review') return review(rest);
|
|
5323
5446
|
}
|
|
5324
5447
|
|
|
5325
|
-
if (command === '
|
|
5326
|
-
|
|
5327
|
-
return;
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
if (command === '
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
if (command === '
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
}
|
|
5349
|
-
|
|
5350
|
-
if (command === 'copy-prompt') {
|
|
5351
|
-
copyPrompt();
|
|
5352
|
-
return;
|
|
5353
|
-
}
|
|
5354
|
-
|
|
5355
|
-
if (command === 'copy-commands') {
|
|
5356
|
-
copyCommands();
|
|
5357
|
-
return;
|
|
5358
|
-
}
|
|
5359
|
-
|
|
5360
|
-
if (command === 'protect') {
|
|
5361
|
-
protect(rest);
|
|
5362
|
-
return;
|
|
5363
|
-
}
|
|
5364
|
-
|
|
5365
|
-
if (command === 'sync') {
|
|
5366
|
-
sync(rest);
|
|
5367
|
-
return;
|
|
5368
|
-
}
|
|
5369
|
-
|
|
5370
|
-
if (command === 'cleanup') {
|
|
5371
|
-
cleanup(rest);
|
|
5372
|
-
return;
|
|
5373
|
-
}
|
|
5374
|
-
|
|
5375
|
-
if (command === 'release') {
|
|
5376
|
-
release(rest);
|
|
5377
|
-
return;
|
|
5378
|
-
}
|
|
5379
|
-
|
|
5380
|
-
if (command === 'install') {
|
|
5381
|
-
install(rest);
|
|
5382
|
-
return;
|
|
5383
|
-
}
|
|
5384
|
-
|
|
5385
|
-
if (command === 'fix') {
|
|
5386
|
-
fix(rest);
|
|
5387
|
-
return;
|
|
5388
|
-
}
|
|
5389
|
-
|
|
5390
|
-
if (command === 'scan') {
|
|
5391
|
-
scan(rest);
|
|
5392
|
-
return;
|
|
5393
|
-
}
|
|
5394
|
-
|
|
5395
|
-
if (command === 'print-agents-snippet') {
|
|
5396
|
-
printAgentsSnippet();
|
|
5397
|
-
return;
|
|
5398
|
-
}
|
|
5448
|
+
if (command === 'status') {
|
|
5449
|
+
const { found: strict, remaining } = extractFlag(rest, '--strict');
|
|
5450
|
+
if (strict) return scan(remaining);
|
|
5451
|
+
return status(remaining);
|
|
5452
|
+
}
|
|
5453
|
+
|
|
5454
|
+
if (command === 'setup') {
|
|
5455
|
+
const installOnly = extractFlag(rest, '--install-only', '--only-install');
|
|
5456
|
+
if (installOnly.found) return install(installOnly.remaining);
|
|
5457
|
+
const repairOnly = extractFlag(installOnly.remaining, '--repair', '--fix-only');
|
|
5458
|
+
if (repairOnly.found) return fix(repairOnly.remaining);
|
|
5459
|
+
return setup(repairOnly.remaining);
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5462
|
+
if (command === 'prompt') return prompt(rest);
|
|
5463
|
+
if (command === 'doctor') return doctor(rest);
|
|
5464
|
+
if (command === 'agents') return agents(rest);
|
|
5465
|
+
if (command === 'finish') return finish(rest);
|
|
5466
|
+
if (command === 'report') return report(rest);
|
|
5467
|
+
if (command === 'protect') return protect(rest);
|
|
5468
|
+
if (command === 'sync') return sync(rest);
|
|
5469
|
+
if (command === 'cleanup') return cleanup(rest);
|
|
5470
|
+
if (command === 'release') return release(rest);
|
|
5399
5471
|
|
|
5400
5472
|
const suggestion = maybeSuggestCommand(command);
|
|
5401
5473
|
if (suggestion) {
|