@imdeadpool/guardex 6.0.1 → 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 +368 -280
- package/package.json +3 -5
- package/templates/AGENTS.multiagent-safety.md +9 -81
- 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/bin/multiagent-safety.js
CHANGED
|
@@ -163,149 +163,84 @@ const COMMAND_TYPO_ALIASES = new Map([
|
|
|
163
163
|
const SUGGESTIBLE_COMMANDS = [
|
|
164
164
|
'status',
|
|
165
165
|
'setup',
|
|
166
|
-
'init',
|
|
167
166
|
'doctor',
|
|
168
|
-
'review',
|
|
169
167
|
'agents',
|
|
170
168
|
'finish',
|
|
171
169
|
'report',
|
|
172
|
-
'copy-prompt',
|
|
173
|
-
'copy-commands',
|
|
174
170
|
'protect',
|
|
175
171
|
'sync',
|
|
176
172
|
'cleanup',
|
|
177
|
-
'
|
|
173
|
+
'prompt',
|
|
174
|
+
'help',
|
|
175
|
+
'version',
|
|
176
|
+
// deprecated aliases still routable with a warning
|
|
177
|
+
'init',
|
|
178
178
|
'install',
|
|
179
179
|
'fix',
|
|
180
180
|
'scan',
|
|
181
|
+
'review',
|
|
182
|
+
'copy-prompt',
|
|
183
|
+
'copy-commands',
|
|
181
184
|
'print-agents-snippet',
|
|
182
|
-
'
|
|
183
|
-
'version',
|
|
185
|
+
'release',
|
|
184
186
|
];
|
|
185
187
|
const CLI_COMMAND_DESCRIPTIONS = [
|
|
186
188
|
['status', 'Show GuardeX CLI + service health without modifying files'],
|
|
187
|
-
['setup', 'Install
|
|
188
|
-
['
|
|
189
|
-
['doctor', 'Repair safety setup drift, then verify repo safety'],
|
|
190
|
-
['report', 'Generate security/safety reports (for example: OpenSSF scorecard)'],
|
|
191
|
-
['finish', 'Auto-commit completed agent branches, then run PR finish flow'],
|
|
192
|
-
['copy-prompt', 'Print the AI-ready setup checklist'],
|
|
193
|
-
['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)'],
|
|
194
191
|
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
|
|
195
|
-
['sync', '
|
|
196
|
-
['
|
|
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'],
|
|
197
195
|
['agents', 'Start/stop repo-scoped review + cleanup bots'],
|
|
198
|
-
['
|
|
199
|
-
['
|
|
200
|
-
['scan', 'Report safety issues and exit non-zero on findings'],
|
|
201
|
-
['print-agents-snippet', 'Print the AGENTS.md snippet template'],
|
|
202
|
-
['release', 'Publish GuardeX from maintainer release repo'],
|
|
196
|
+
['prompt', 'Print AI setup checklist (--exec, --snippet)'],
|
|
197
|
+
['report', 'Security/safety reports (e.g. OpenSSF scorecard)'],
|
|
203
198
|
['help', 'Show this help output'],
|
|
204
199
|
['version', 'Print GuardeX version'],
|
|
205
200
|
];
|
|
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)' }],
|
|
210
|
+
]);
|
|
206
211
|
const AGENT_BOT_DESCRIPTIONS = [
|
|
207
|
-
['
|
|
208
|
-
['agents', 'Start/stop both review and cleanup bots for this repo'],
|
|
212
|
+
['agents', 'Start/stop review + cleanup bots for this repo'],
|
|
209
213
|
];
|
|
210
214
|
|
|
211
|
-
const AI_SETUP_PROMPT = `
|
|
212
|
-
|
|
213
|
-
1) Install
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
4) Optional: start continuous PR monitor from this repo:
|
|
231
|
-
gx review --interval 30
|
|
232
|
-
|
|
233
|
-
5) Confirm next safe agent workflow commands:
|
|
234
|
-
bash scripts/codex-agent.sh "task" "agent-name"
|
|
235
|
-
bash scripts/agent-branch-start.sh "task" "agent-name"
|
|
236
|
-
python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
|
|
237
|
-
bash scripts/agent-branch-finish.sh --branch "$(git rev-parse --abbrev-ref HEAD)" --base dev --via-pr --wait-for-merge
|
|
238
|
-
- For every new user message/task, repeat the same cycle:
|
|
239
|
-
start isolated agent branch/worktree -> claim file locks -> implement/verify ->
|
|
240
|
-
finish via PR/merge cleanup into dev with scripts/agent-branch-finish.sh.
|
|
241
|
-
- Finished branches stay available by default for audit/follow-up.
|
|
242
|
-
Remove them explicitly when done:
|
|
243
|
-
gx cleanup --branch "$(git rev-parse --abbrev-ref HEAD)"
|
|
244
|
-
- To finalize all completed agent branches in one pass:
|
|
245
|
-
gx finish --all
|
|
246
|
-
|
|
247
|
-
6) OpenSpec default change flow (core profile):
|
|
248
|
-
/opsx:propose <change-name>
|
|
249
|
-
/opsx:apply
|
|
250
|
-
/opsx:archive
|
|
251
|
-
- Full guide: docs/openspec-getting-started.md
|
|
252
|
-
|
|
253
|
-
7) Optional: enable expanded OpenSpec workflow commands:
|
|
254
|
-
openspec config profile <profile-name>
|
|
255
|
-
openspec update
|
|
256
|
-
- Expanded path: /opsx:new -> /opsx:ff or /opsx:continue -> /opsx:apply -> /opsx:verify -> /opsx:archive
|
|
257
|
-
|
|
258
|
-
8) Optional: create OpenSpec planning workspace:
|
|
259
|
-
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
|
|
260
|
-
|
|
261
|
-
9) Optional: protect extra branches:
|
|
262
|
-
gx protect add release staging
|
|
263
|
-
|
|
264
|
-
10) Optional: sync your current agent branch with latest base branch:
|
|
265
|
-
gx sync --check
|
|
266
|
-
gx sync
|
|
267
|
-
|
|
268
|
-
11) Optional (GitHub remote cleanup): enable:
|
|
269
|
-
Settings -> General -> Pull Requests -> Automatically delete head branches
|
|
270
|
-
|
|
271
|
-
12) Optional (fork sync with Pull app):
|
|
272
|
-
cp .github/pull.yml.example .github/pull.yml
|
|
273
|
-
# then edit .github/pull.yml:
|
|
274
|
-
# - set rules[].base to your fork branch (main/master/dev)
|
|
275
|
-
# - set rules[].upstream to upstream-owner:branch
|
|
276
|
-
# install app: https://github.com/apps/pull
|
|
277
|
-
# validate config: https://pull.git.ci/check/<owner>/<repo>
|
|
278
|
-
|
|
279
|
-
13) Optional (PR review bot with cr-gpt GitHub App):
|
|
280
|
-
- install app: https://github.com/apps/cr-gpt
|
|
281
|
-
- in GitHub repo Settings -> Secrets and variables -> Actions -> Variables:
|
|
282
|
-
add OPENAI_API_KEY (your API key)
|
|
283
|
-
- the app reviews new/updated pull requests automatically
|
|
284
|
-
|
|
285
|
-
14) Optional: test PR review action workflow
|
|
286
|
-
- gx setup installs .github/workflows/cr.yml
|
|
287
|
-
- open or update a PR
|
|
288
|
-
- 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
|
|
289
233
|
`;
|
|
290
234
|
|
|
291
235
|
const AI_SETUP_COMMANDS = `npm i -g @imdeadpool/guardex
|
|
292
236
|
gh --version
|
|
293
237
|
gx setup
|
|
294
238
|
gx doctor
|
|
295
|
-
|
|
296
|
-
bash scripts/codex-agent.sh "task" "agent-name"
|
|
297
|
-
bash scripts/agent-branch-start.sh "task" "agent-name"
|
|
298
|
-
python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
|
|
299
|
-
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>"
|
|
300
240
|
gx finish --all
|
|
301
|
-
gx cleanup
|
|
302
|
-
bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
|
|
303
|
-
openspec config profile <profile-name>
|
|
304
|
-
openspec update
|
|
241
|
+
gx cleanup
|
|
305
242
|
gx protect add release staging
|
|
306
|
-
gx sync --check
|
|
307
243
|
gx sync
|
|
308
|
-
cp .github/pull.yml.example .github/pull.yml
|
|
309
244
|
`;
|
|
310
245
|
|
|
311
246
|
const SCORECARD_RISK_BY_CHECK = {
|
|
@@ -439,19 +374,12 @@ AGENT BOT
|
|
|
439
374
|
${agentBotCatalogLines().join('\n')}
|
|
440
375
|
|
|
441
376
|
NOTES
|
|
442
|
-
-
|
|
443
|
-
-
|
|
444
|
-
-
|
|
445
|
-
-
|
|
446
|
-
- ${
|
|
447
|
-
-
|
|
448
|
-
- Optional parent-folder Source Control view: ${SHORT_TOOL_NAME} setup --target <repo-path> --parent-workspace-view
|
|
449
|
-
- In initialized repos, setup/install/fix block in-place writes on protected main by default
|
|
450
|
-
- setup/doctor auto-finish clean pending agent/* branches via PR flow into the current local base branch
|
|
451
|
-
- doctor auto-runs in a sandbox agent branch/worktree on protected main and tries auto-finish PR flow
|
|
452
|
-
- agent-branch-finish merges by default and keeps agent branches/worktrees until explicit cleanup
|
|
453
|
-
- use '${SHORT_TOOL_NAME} cleanup' to remove merged agent branches/worktrees (optionally remote refs too)
|
|
454
|
-
- 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(', ')}.`);
|
|
455
383
|
|
|
456
384
|
if (outsideGitRepo) {
|
|
457
385
|
console.log(`
|
|
@@ -497,6 +425,90 @@ function isGitRepo(targetPath) {
|
|
|
497
425
|
return result.status === 0;
|
|
498
426
|
}
|
|
499
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
|
+
|
|
500
512
|
function toDestinationPath(relativeTemplatePath) {
|
|
501
513
|
if (relativeTemplatePath.startsWith('scripts/')) {
|
|
502
514
|
return relativeTemplatePath;
|
|
@@ -693,7 +705,8 @@ function writeLockState(repoRoot, payload, dryRun) {
|
|
|
693
705
|
fs.writeFileSync(lockPath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
694
706
|
}
|
|
695
707
|
|
|
696
|
-
function ensurePackageScripts(repoRoot, dryRun) {
|
|
708
|
+
function ensurePackageScripts(repoRoot, dryRun, options = {}) {
|
|
709
|
+
const force = Boolean(options.force);
|
|
697
710
|
const packagePath = path.join(repoRoot, 'package.json');
|
|
698
711
|
if (!fs.existsSync(packagePath)) {
|
|
699
712
|
return { status: 'skipped', file: 'package.json', note: 'package.json not found' };
|
|
@@ -706,30 +719,15 @@ function ensurePackageScripts(repoRoot, dryRun) {
|
|
|
706
719
|
throw new Error(`Unable to parse package.json in target repo: ${error.message}`);
|
|
707
720
|
}
|
|
708
721
|
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
'
|
|
715
|
-
|
|
716
|
-
'agent:hooks:install': 'bash ./scripts/install-agent-git-hooks.sh',
|
|
717
|
-
'agent:locks:claim': 'python3 ./scripts/agent-file-locks.py claim',
|
|
718
|
-
'agent:locks:allow-delete': 'python3 ./scripts/agent-file-locks.py allow-delete',
|
|
719
|
-
'agent:locks:release': 'python3 ./scripts/agent-file-locks.py release',
|
|
720
|
-
'agent:locks:status': 'python3 ./scripts/agent-file-locks.py status',
|
|
721
|
-
'agent:plan:init': 'bash ./scripts/openspec/init-plan-workspace.sh',
|
|
722
|
-
'agent:change:init': 'bash ./scripts/openspec/init-change-workspace.sh',
|
|
723
|
-
'agent:protect:list': `${SHORT_TOOL_NAME} protect list`,
|
|
724
|
-
'agent:branch:sync': `${SHORT_TOOL_NAME} sync`,
|
|
725
|
-
'agent:branch:sync:check': `${SHORT_TOOL_NAME} sync --check`,
|
|
726
|
-
'agent:safety:setup': `${SHORT_TOOL_NAME} setup`,
|
|
727
|
-
'agent:safety:scan': `${SHORT_TOOL_NAME} scan`,
|
|
728
|
-
'agent:safety:fix': `${SHORT_TOOL_NAME} fix`,
|
|
729
|
-
'agent:safety:doctor': `${SHORT_TOOL_NAME} doctor`,
|
|
730
|
-
};
|
|
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
|
+
}
|
|
731
729
|
|
|
732
|
-
pkg.scripts =
|
|
730
|
+
pkg.scripts = existingScripts;
|
|
733
731
|
let changed = false;
|
|
734
732
|
for (const [key, value] of Object.entries(REQUIRED_PACKAGE_SCRIPTS)) {
|
|
735
733
|
if (pkg.scripts[key] !== value) {
|
|
@@ -749,7 +747,8 @@ function ensurePackageScripts(repoRoot, dryRun) {
|
|
|
749
747
|
return { status: 'updated', file: 'package.json' };
|
|
750
748
|
}
|
|
751
749
|
|
|
752
|
-
function ensureAgentsSnippet(repoRoot, dryRun) {
|
|
750
|
+
function ensureAgentsSnippet(repoRoot, dryRun, options = {}) {
|
|
751
|
+
const force = Boolean(options.force);
|
|
753
752
|
const agentsPath = path.join(repoRoot, 'AGENTS.md');
|
|
754
753
|
const snippet = fs.readFileSync(path.join(TEMPLATE_ROOT, 'AGENTS.multiagent-safety.md'), 'utf8').trimEnd();
|
|
755
754
|
const managedRegex = new RegExp(
|
|
@@ -766,6 +765,9 @@ function ensureAgentsSnippet(repoRoot, dryRun) {
|
|
|
766
765
|
|
|
767
766
|
const existing = fs.readFileSync(agentsPath, 'utf8');
|
|
768
767
|
if (managedRegex.test(existing)) {
|
|
768
|
+
if (!force) {
|
|
769
|
+
return { status: 'unchanged', file: 'AGENTS.md', note: 'preserved existing guardex-managed block' };
|
|
770
|
+
}
|
|
769
771
|
const next = existing.replace(managedRegex, snippet);
|
|
770
772
|
if (next === existing) {
|
|
771
773
|
return { status: 'unchanged', file: 'AGENTS.md' };
|
|
@@ -909,10 +911,18 @@ function parseCommonArgs(rawArgs, defaults) {
|
|
|
909
911
|
}
|
|
910
912
|
|
|
911
913
|
function parseSetupArgs(rawArgs, defaults) {
|
|
912
|
-
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
|
+
};
|
|
913
922
|
const forwardedArgs = [];
|
|
914
923
|
|
|
915
|
-
for (
|
|
924
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
925
|
+
const arg = rawArgs[index];
|
|
916
926
|
if (arg === '--parent-workspace-view') {
|
|
917
927
|
setupDefaults.parentWorkspaceView = true;
|
|
918
928
|
continue;
|
|
@@ -921,6 +931,34 @@ function parseSetupArgs(rawArgs, defaults) {
|
|
|
921
931
|
setupDefaults.parentWorkspaceView = false;
|
|
922
932
|
continue;
|
|
923
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
|
+
}
|
|
924
962
|
forwardedArgs.push(arg);
|
|
925
963
|
}
|
|
926
964
|
|
|
@@ -1076,6 +1114,7 @@ function resolveSandboxTarget(repoRoot, worktreePath, targetPath) {
|
|
|
1076
1114
|
function buildSandboxDoctorArgs(options, sandboxTarget) {
|
|
1077
1115
|
const args = ['doctor', '--target', sandboxTarget];
|
|
1078
1116
|
if (options.dryRun) args.push('--dry-run');
|
|
1117
|
+
if (options.force) args.push('--force');
|
|
1079
1118
|
if (options.skipAgents) args.push('--skip-agents');
|
|
1080
1119
|
if (options.skipPackageJson) args.push('--skip-package-json');
|
|
1081
1120
|
if (options.skipGitignore) args.push('--no-gitignore');
|
|
@@ -3512,11 +3551,11 @@ function runInstallInternal(options) {
|
|
|
3512
3551
|
}
|
|
3513
3552
|
|
|
3514
3553
|
if (!options.skipPackageJson) {
|
|
3515
|
-
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun)));
|
|
3554
|
+
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3516
3555
|
}
|
|
3517
3556
|
|
|
3518
3557
|
if (!options.skipAgents) {
|
|
3519
|
-
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun)));
|
|
3558
|
+
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3520
3559
|
}
|
|
3521
3560
|
|
|
3522
3561
|
const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
|
|
@@ -3566,11 +3605,11 @@ function runFixInternal(options) {
|
|
|
3566
3605
|
}
|
|
3567
3606
|
|
|
3568
3607
|
if (!options.skipPackageJson) {
|
|
3569
|
-
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun)));
|
|
3608
|
+
operations.push(ensurePackageScripts(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3570
3609
|
}
|
|
3571
3610
|
|
|
3572
3611
|
if (!options.skipAgents) {
|
|
3573
|
-
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun)));
|
|
3612
|
+
operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
|
|
3574
3613
|
}
|
|
3575
3614
|
|
|
3576
3615
|
const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
|
|
@@ -4437,23 +4476,87 @@ function setup(rawArgs) {
|
|
|
4437
4476
|
}
|
|
4438
4477
|
}
|
|
4439
4478
|
|
|
4440
|
-
|
|
4441
|
-
const
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
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
|
+
}
|
|
4445
4496
|
}
|
|
4446
|
-
printOperations('Setup/install', installPayload, options.dryRun);
|
|
4447
4497
|
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
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
|
+
}
|
|
4457
4560
|
|
|
4458
4561
|
if (options.dryRun) {
|
|
4459
4562
|
console.log(`[${TOOL_NAME}] Dry run setup done.`);
|
|
@@ -4461,32 +4564,11 @@ function setup(rawArgs) {
|
|
|
4461
4564
|
return;
|
|
4462
4565
|
}
|
|
4463
4566
|
|
|
4464
|
-
if (
|
|
4465
|
-
const
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
const scanResult = runScanInternal({ target: options.target, json: false });
|
|
4470
|
-
const currentBaseBranch = currentBranchName(scanResult.repoRoot);
|
|
4471
|
-
const autoFinishSummary = autoFinishReadyAgentBranches(scanResult.repoRoot, {
|
|
4472
|
-
baseBranch: currentBaseBranch,
|
|
4473
|
-
dryRun: options.dryRun,
|
|
4474
|
-
});
|
|
4475
|
-
printScanResult(scanResult, false);
|
|
4476
|
-
if (autoFinishSummary.enabled) {
|
|
4477
|
-
console.log(
|
|
4478
|
-
`[${TOOL_NAME}] Auto-finish sweep (base=${currentBaseBranch}): attempted=${autoFinishSummary.attempted}, completed=${autoFinishSummary.completed}, skipped=${autoFinishSummary.skipped}, failed=${autoFinishSummary.failed}`,
|
|
4479
|
-
);
|
|
4480
|
-
for (const detail of autoFinishSummary.details) {
|
|
4481
|
-
console.log(`[${TOOL_NAME}] ${detail}`);
|
|
4482
|
-
}
|
|
4483
|
-
} else if (autoFinishSummary.details.length > 0) {
|
|
4484
|
-
console.log(`[${TOOL_NAME}] ${autoFinishSummary.details[0]}`);
|
|
4485
|
-
}
|
|
4486
|
-
|
|
4487
|
-
if (scanResult.errors === 0 && scanResult.warnings === 0) {
|
|
4488
|
-
console.log(`[${TOOL_NAME}] ✅ Setup complete.`);
|
|
4489
|
-
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`);
|
|
4490
4572
|
console.log(
|
|
4491
4573
|
`[${TOOL_NAME}] OpenSpec core workflow: /opsx:propose -> /opsx:apply -> /opsx:archive`,
|
|
4492
4574
|
);
|
|
@@ -4496,7 +4578,13 @@ function setup(rawArgs) {
|
|
|
4496
4578
|
console.log(`[${TOOL_NAME}] OpenSpec guide: docs/openspec-getting-started.md`);
|
|
4497
4579
|
}
|
|
4498
4580
|
|
|
4499
|
-
|
|
4581
|
+
if (lastScanResult) {
|
|
4582
|
+
setExitCodeFromScan({
|
|
4583
|
+
...lastScanResult,
|
|
4584
|
+
errors: aggregateErrors,
|
|
4585
|
+
warnings: aggregateWarnings,
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
4500
4588
|
}
|
|
4501
4589
|
|
|
4502
4590
|
function ensureMainBranch(repoRoot) {
|
|
@@ -4795,6 +4883,31 @@ function copyCommands() {
|
|
|
4795
4883
|
process.exitCode = 0;
|
|
4796
4884
|
}
|
|
4797
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
|
+
|
|
4798
4911
|
function cleanup(rawArgs) {
|
|
4799
4912
|
const options = parseCleanupArgs(rawArgs);
|
|
4800
4913
|
const repoRoot = resolveRepoRoot(options.target);
|
|
@@ -5273,6 +5386,29 @@ function normalizeCommandOrThrow(command) {
|
|
|
5273
5386
|
return command;
|
|
5274
5387
|
}
|
|
5275
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
|
+
|
|
5276
5412
|
function main() {
|
|
5277
5413
|
const args = process.argv.slice(2);
|
|
5278
5414
|
|
|
@@ -5296,90 +5432,42 @@ function main() {
|
|
|
5296
5432
|
return;
|
|
5297
5433
|
}
|
|
5298
5434
|
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
return;
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
doctor(rest);
|
|
5311
|
-
return;
|
|
5312
|
-
}
|
|
5313
|
-
|
|
5314
|
-
if (command === 'review') {
|
|
5315
|
-
review(rest);
|
|
5316
|
-
return;
|
|
5317
|
-
}
|
|
5318
|
-
|
|
5319
|
-
if (command === 'agents') {
|
|
5320
|
-
agents(rest);
|
|
5321
|
-
return;
|
|
5322
|
-
}
|
|
5323
|
-
|
|
5324
|
-
if (command === 'finish') {
|
|
5325
|
-
finish(rest);
|
|
5326
|
-
return;
|
|
5327
|
-
}
|
|
5328
|
-
|
|
5329
|
-
if (command === 'report') {
|
|
5330
|
-
report(rest);
|
|
5331
|
-
return;
|
|
5332
|
-
}
|
|
5333
|
-
|
|
5334
|
-
if (command === 'copy-prompt') {
|
|
5335
|
-
copyPrompt();
|
|
5336
|
-
return;
|
|
5337
|
-
}
|
|
5338
|
-
|
|
5339
|
-
if (command === 'copy-commands') {
|
|
5340
|
-
copyCommands();
|
|
5341
|
-
return;
|
|
5342
|
-
}
|
|
5343
|
-
|
|
5344
|
-
if (command === 'protect') {
|
|
5345
|
-
protect(rest);
|
|
5346
|
-
return;
|
|
5347
|
-
}
|
|
5348
|
-
|
|
5349
|
-
if (command === 'sync') {
|
|
5350
|
-
sync(rest);
|
|
5351
|
-
return;
|
|
5352
|
-
}
|
|
5353
|
-
|
|
5354
|
-
if (command === 'cleanup') {
|
|
5355
|
-
cleanup(rest);
|
|
5356
|
-
return;
|
|
5357
|
-
}
|
|
5358
|
-
|
|
5359
|
-
if (command === 'release') {
|
|
5360
|
-
release(rest);
|
|
5361
|
-
return;
|
|
5362
|
-
}
|
|
5363
|
-
|
|
5364
|
-
if (command === 'install') {
|
|
5365
|
-
install(rest);
|
|
5366
|
-
return;
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
|
-
if (command === 'fix') {
|
|
5370
|
-
fix(rest);
|
|
5371
|
-
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);
|
|
5372
5446
|
}
|
|
5373
5447
|
|
|
5374
|
-
if (command === '
|
|
5375
|
-
|
|
5376
|
-
return;
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
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);
|
|
5383
5471
|
|
|
5384
5472
|
const suggestion = maybeSuggestCommand(command);
|
|
5385
5473
|
if (suggestion) {
|