aiwcli 0.9.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 +1248 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +16 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +19 -0
- package/dist/commands/branch.d.ts +45 -0
- package/dist/commands/branch.js +488 -0
- package/dist/commands/clean.d.ts +34 -0
- package/dist/commands/clean.js +186 -0
- package/dist/commands/clear.d.ts +51 -0
- package/dist/commands/clear.js +835 -0
- package/dist/commands/init/index.d.ts +107 -0
- package/dist/commands/init/index.js +565 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-command.d.ts +114 -0
- package/dist/lib/base-command.js +153 -0
- package/dist/lib/bmad-installer.d.ts +38 -0
- package/dist/lib/bmad-installer.js +145 -0
- package/dist/lib/claude-settings-types.d.ts +102 -0
- package/dist/lib/claude-settings-types.js +5 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/debug.d.ts +39 -0
- package/dist/lib/debug.js +74 -0
- package/dist/lib/env-compat.d.ts +26 -0
- package/dist/lib/env-compat.js +35 -0
- package/dist/lib/errors.d.ts +126 -0
- package/dist/lib/errors.js +145 -0
- package/dist/lib/generic-merge.d.ts +74 -0
- package/dist/lib/generic-merge.js +105 -0
- package/dist/lib/git/branch.d.ts +67 -0
- package/dist/lib/git/branch.js +155 -0
- package/dist/lib/git/index.d.ts +11 -0
- package/dist/lib/git/index.js +13 -0
- package/dist/lib/git/safety-checks.d.ts +44 -0
- package/dist/lib/git/safety-checks.js +102 -0
- package/dist/lib/git/types.d.ts +31 -0
- package/dist/lib/git/types.js +6 -0
- package/dist/lib/git/worktree.d.ts +67 -0
- package/dist/lib/git/worktree.js +220 -0
- package/dist/lib/gitignore-manager.d.ts +10 -0
- package/dist/lib/gitignore-manager.js +60 -0
- package/dist/lib/hooks-merger.d.ts +28 -0
- package/dist/lib/hooks-merger.js +94 -0
- package/dist/lib/ide-path-resolver.d.ts +102 -0
- package/dist/lib/ide-path-resolver.js +129 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +22 -0
- package/dist/lib/output.d.ts +51 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/paths.d.ts +66 -0
- package/dist/lib/paths.js +136 -0
- package/dist/lib/quiet.d.ts +12 -0
- package/dist/lib/quiet.js +17 -0
- package/dist/lib/settings-hierarchy.d.ts +42 -0
- package/dist/lib/settings-hierarchy.js +105 -0
- package/dist/lib/spawn.d.ts +105 -0
- package/dist/lib/spawn.js +157 -0
- package/dist/lib/spinner.d.ts +19 -0
- package/dist/lib/spinner.js +34 -0
- package/dist/lib/stdin.d.ts +48 -0
- package/dist/lib/stdin.js +60 -0
- package/dist/lib/template-installer.d.ts +92 -0
- package/dist/lib/template-installer.js +375 -0
- package/dist/lib/template-linter.d.ts +49 -0
- package/dist/lib/template-linter.js +173 -0
- package/dist/lib/template-merger.d.ts +47 -0
- package/dist/lib/template-merger.js +173 -0
- package/dist/lib/template-resolver.d.ts +20 -0
- package/dist/lib/template-resolver.js +60 -0
- package/dist/lib/terminal.d.ts +102 -0
- package/dist/lib/terminal.js +245 -0
- package/dist/lib/tty-detection.d.ts +62 -0
- package/dist/lib/tty-detection.js +83 -0
- package/dist/lib/user-utils.d.ts +5 -0
- package/dist/lib/user-utils.js +23 -0
- package/dist/lib/version.d.ts +99 -0
- package/dist/lib/version.js +144 -0
- package/dist/lib/watch-templates.d.ts +6 -0
- package/dist/lib/watch-templates.js +73 -0
- package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
- package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
- package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
- package/dist/lib/windsurf-hooks-merger.js +53 -0
- package/dist/lib/windsurf-hooks-types.d.ts +33 -0
- package/dist/lib/windsurf-hooks-types.js +5 -0
- package/dist/templates/CLAUDE.md +174 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
- package/dist/templates/_shared/.claude/settings.json +61 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
- package/dist/templates/_shared/hooks/__init__.py +16 -0
- package/dist/templates/_shared/hooks/archive_plan.py +270 -0
- package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
- package/dist/templates/_shared/hooks/context_monitor.py +322 -0
- package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
- package/dist/templates/_shared/lib/__init__.py +1 -0
- package/dist/templates/_shared/lib/base/__init__.py +49 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
- package/dist/templates/_shared/lib/base/constants.py +299 -0
- package/dist/templates/_shared/lib/base/inference.py +189 -0
- package/dist/templates/_shared/lib/base/utils.py +216 -0
- package/dist/templates/_shared/lib/context/__init__.py +119 -0
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/cache.py +446 -0
- package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
- package/dist/templates/_shared/lib/context/discovery.py +486 -0
- package/dist/templates/_shared/lib/context/event_log.py +308 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
- package/dist/templates/_shared/lib/context/task_sync.py +367 -0
- package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
- package/dist/templates/_shared/lib/templates/README.md +215 -0
- package/dist/templates/_shared/lib/templates/__init__.py +40 -0
- package/dist/templates/_shared/lib/templates/formatters.py +147 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
- package/dist/templates/_shared/scripts/save_handoff.py +99 -0
- package/dist/templates/_shared/workflows/handoff.md +212 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
- package/dist/templates/cc-native/.claude/settings.json +119 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
- package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
- package/dist/templates/cc-native/MIGRATION.md +86 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
- package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
- package/dist/types/exit-codes.d.ts +11 -0
- package/dist/types/exit-codes.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/oclif.manifest.json +405 -0
- package/package.json +109 -0
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning
|
|
2
|
+
|
|
3
|
+
import {execute} from '@oclif/core'
|
|
4
|
+
|
|
5
|
+
// Handle default command: inject 'launch' when no command is specified
|
|
6
|
+
// but preserve --help and --version behavior for general CLI help
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
const firstArg = args[0] ?? ''
|
|
9
|
+
const hasCommand = args.length > 0 && !firstArg.startsWith('-')
|
|
10
|
+
const isHelpOrVersion = firstArg === '--help' || firstArg === '-h' || firstArg === '--version'
|
|
11
|
+
|
|
12
|
+
await execute({
|
|
13
|
+
development: true,
|
|
14
|
+
dir: import.meta.url,
|
|
15
|
+
args: hasCommand || isHelpOrVersion ? args : ['launch', ...args],
|
|
16
|
+
})
|
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Load environment variable compatibility layer first
|
|
4
|
+
const {loadEnvWithCompatibility} = await import('../dist/lib/env-compat.js')
|
|
5
|
+
loadEnvWithCompatibility()
|
|
6
|
+
|
|
7
|
+
import {execute} from '@oclif/core'
|
|
8
|
+
|
|
9
|
+
// Handle default command: inject 'launch' when no command is specified
|
|
10
|
+
// but preserve --help and --version behavior for general CLI help
|
|
11
|
+
const args = process.argv.slice(2)
|
|
12
|
+
const firstArg = args[0] ?? ''
|
|
13
|
+
const hasCommand = args.length > 0 && !firstArg.startsWith('-')
|
|
14
|
+
const isHelpOrVersion = firstArg === '--help' || firstArg === '-h' || firstArg === '--version'
|
|
15
|
+
|
|
16
|
+
await execute({
|
|
17
|
+
dir: import.meta.url,
|
|
18
|
+
args: hasCommand || isHelpOrVersion ? args : ['launch', ...args],
|
|
19
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import BaseCommand from '../lib/base-command.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manage git branch operations: launch in main/master or delete branch and worktree.
|
|
4
|
+
*
|
|
5
|
+
* This command supports two modes:
|
|
6
|
+
* 1. --main: Opens a new terminal window with `aiw launch` running in the main/master branch
|
|
7
|
+
* 2. --delete: Deletes a git branch and its worktree folder
|
|
8
|
+
*/
|
|
9
|
+
export default class BranchCommand extends BaseCommand {
|
|
10
|
+
static args: {
|
|
11
|
+
branchName: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
static description: string;
|
|
14
|
+
static examples: string[];
|
|
15
|
+
static flags: {
|
|
16
|
+
main: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
launch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
delete: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
help: import("@oclif/core/interfaces").BooleanFlag<void>;
|
|
22
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
23
|
+
};
|
|
24
|
+
run(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Handle --delete flag: Delete git branch and worktree folder
|
|
27
|
+
*/
|
|
28
|
+
private handleDelete;
|
|
29
|
+
/**
|
|
30
|
+
* Handle --delete --all flags: Clean up all worktrees safely
|
|
31
|
+
*/
|
|
32
|
+
private handleDeleteAll;
|
|
33
|
+
/**
|
|
34
|
+
* Handle --main flag: Launch aiw in main/master branch
|
|
35
|
+
*/
|
|
36
|
+
private handleMainBranch;
|
|
37
|
+
/**
|
|
38
|
+
* Handle --launch flag: Create or open worktree in sibling folder
|
|
39
|
+
*/
|
|
40
|
+
private handleWorktreeLaunch;
|
|
41
|
+
/**
|
|
42
|
+
* Check if current directory is a git repository
|
|
43
|
+
*/
|
|
44
|
+
private isGitRepository;
|
|
45
|
+
}
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { Args, Flags } from '@oclif/core';
|
|
4
|
+
import clipboardy from 'clipboardy';
|
|
5
|
+
import BaseCommand from '../lib/base-command.js';
|
|
6
|
+
import { branchExists, createWorktree, deleteBranch, deleteWorktreeFolder, getAllWorktrees, getCurrentBranch, getMainBranch, getWorktreePath, hasMergeRequest, hasUnpushedCommits, } from '../lib/git/index.js';
|
|
7
|
+
import { launchTerminal } from '../lib/terminal.js';
|
|
8
|
+
import { EXIT_CODES } from '../types/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Manage git branch operations: launch in main/master or delete branch and worktree.
|
|
11
|
+
*
|
|
12
|
+
* This command supports two modes:
|
|
13
|
+
* 1. --main: Opens a new terminal window with `aiw launch` running in the main/master branch
|
|
14
|
+
* 2. --delete: Deletes a git branch and its worktree folder
|
|
15
|
+
*/
|
|
16
|
+
export default class BranchCommand extends BaseCommand {
|
|
17
|
+
static args = {
|
|
18
|
+
branchName: Args.string({
|
|
19
|
+
description: 'Name of the branch for worktree creation or deletion',
|
|
20
|
+
required: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static description = 'Manage git branches with worktree support or launch in main/master\n\n' +
|
|
24
|
+
'MODES\n' +
|
|
25
|
+
' --main/-m: Launch aiw in main/master branch in new terminal\n' +
|
|
26
|
+
' --launch/-l <branch>: Create/open git worktree in sibling folder\n' +
|
|
27
|
+
' --delete/-d <branch>: Delete git branch and worktree folder\n' +
|
|
28
|
+
' --delete --all: Clean up all worktrees (soft delete, safe mode)\n\n' +
|
|
29
|
+
'SOFT DELETE (--delete --all)\n' +
|
|
30
|
+
' Safely removes worktrees that meet ALL criteria:\n' +
|
|
31
|
+
' • Not main/master branch\n' +
|
|
32
|
+
' • No unpushed commits to remote\n' +
|
|
33
|
+
' • No open pull requests\n' +
|
|
34
|
+
' • Not the current working directory\n' +
|
|
35
|
+
' Outputs summary of deleted and preserved worktrees\n\n' +
|
|
36
|
+
'REQUIREMENTS\n' +
|
|
37
|
+
' • Must be in a git repository\n' +
|
|
38
|
+
' • For --main: Must be on a branch (not already on main/master)\n' +
|
|
39
|
+
' • For --main: main or master branch must exist\n' +
|
|
40
|
+
' • For --delete: Must not be in the branch being deleted\n\n' +
|
|
41
|
+
'EXIT CODES\n' +
|
|
42
|
+
' 0 Success - Operation completed\n' +
|
|
43
|
+
' 1 General error - unexpected runtime failure\n' +
|
|
44
|
+
' 2 Invalid usage - requirements not met\n' +
|
|
45
|
+
' 3 Environment error - git not found or not a git repository';
|
|
46
|
+
static examples = [
|
|
47
|
+
'<%= config.bin %> <%= command.id %> --main',
|
|
48
|
+
'<%= config.bin %> <%= command.id %> --main --debug # Enable verbose logging',
|
|
49
|
+
'<%= config.bin %> <%= command.id %> --launch feature-name',
|
|
50
|
+
'<%= config.bin %> <%= command.id %> -l fix-bug-123',
|
|
51
|
+
'<%= config.bin %> <%= command.id %> --delete feature-branch',
|
|
52
|
+
'<%= config.bin %> <%= command.id %> -d fix-bug-123',
|
|
53
|
+
'<%= config.bin %> <%= command.id %> --delete --all # Clean up all safe-to-delete worktrees',
|
|
54
|
+
'<%= config.bin %> <%= command.id %> -d -a # Same as above, using short flags',
|
|
55
|
+
];
|
|
56
|
+
static flags = {
|
|
57
|
+
...BaseCommand.baseFlags,
|
|
58
|
+
main: Flags.boolean({
|
|
59
|
+
char: 'm',
|
|
60
|
+
description: 'Launch aiw in main/master branch in new terminal',
|
|
61
|
+
exclusive: ['launch', 'delete'],
|
|
62
|
+
}),
|
|
63
|
+
launch: Flags.boolean({
|
|
64
|
+
char: 'l',
|
|
65
|
+
description: 'Create git worktree in sibling folder or open if exists',
|
|
66
|
+
exclusive: ['main', 'delete'],
|
|
67
|
+
}),
|
|
68
|
+
delete: Flags.boolean({
|
|
69
|
+
char: 'd',
|
|
70
|
+
description: 'Delete git branch and worktree folder',
|
|
71
|
+
exclusive: ['main', 'launch'],
|
|
72
|
+
}),
|
|
73
|
+
all: Flags.boolean({
|
|
74
|
+
char: 'a',
|
|
75
|
+
description: 'With --delete: clean up all worktrees (soft delete, skips unpushed commits and open PRs)',
|
|
76
|
+
dependsOn: ['delete'],
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
async run() {
|
|
80
|
+
const { args, flags } = await this.parse(BranchCommand);
|
|
81
|
+
// Validate that one of the flags is provided
|
|
82
|
+
if (!flags.main && !flags.launch && !flags.delete) {
|
|
83
|
+
this.error('Either --main, --launch, or --delete flag is required', { exit: EXIT_CODES.INVALID_USAGE });
|
|
84
|
+
}
|
|
85
|
+
// Route to appropriate handler
|
|
86
|
+
if (flags.main) {
|
|
87
|
+
await this.handleMainBranch();
|
|
88
|
+
}
|
|
89
|
+
else if (flags.launch) {
|
|
90
|
+
await this.handleWorktreeLaunch(args.branchName);
|
|
91
|
+
}
|
|
92
|
+
else if (flags.delete && flags.all) {
|
|
93
|
+
// Handle --delete --all: clean up all worktrees
|
|
94
|
+
await this.handleDeleteAll();
|
|
95
|
+
}
|
|
96
|
+
else if (flags.delete) {
|
|
97
|
+
// Handle --delete <branchName>: delete single branch
|
|
98
|
+
await this.handleDelete(args.branchName);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Handle --delete flag: Delete git branch and worktree folder
|
|
103
|
+
*/
|
|
104
|
+
async handleDelete(branchName) {
|
|
105
|
+
try {
|
|
106
|
+
// Validate branch name is provided
|
|
107
|
+
if (!branchName) {
|
|
108
|
+
this.error('Branch name is required with --delete flag\n\nUsage: aiw branch --delete <branchName>', {
|
|
109
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const cwd = process.cwd();
|
|
113
|
+
// Check 1: Verify we are in a git repository
|
|
114
|
+
this.debug('Checking if current directory is a git repository...');
|
|
115
|
+
const isGitRepo = await this.isGitRepository(cwd);
|
|
116
|
+
if (!isGitRepo) {
|
|
117
|
+
this.error('Not a git repository. This command only works inside a git repository.', {
|
|
118
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
this.debug('✓ Git repository detected');
|
|
122
|
+
// Check 2: Prevent deletion of main/master branches
|
|
123
|
+
if (branchName === 'main' || branchName === 'master') {
|
|
124
|
+
this.error(`Cannot delete ${branchName} branch. This is a protected branch.`, {
|
|
125
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Check 3: Verify the branch exists
|
|
129
|
+
this.debug(`Checking if branch '${branchName}' exists...`);
|
|
130
|
+
if (!branchExists(branchName)) {
|
|
131
|
+
this.error(`Branch '${branchName}' does not exist.`, {
|
|
132
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
this.debug(`✓ Branch '${branchName}' exists`);
|
|
136
|
+
// Check 4: Get current branch to verify we're not on the branch being deleted
|
|
137
|
+
this.debug('Getting current branch name...');
|
|
138
|
+
let currentBranch;
|
|
139
|
+
try {
|
|
140
|
+
currentBranch = getCurrentBranch();
|
|
141
|
+
}
|
|
142
|
+
catch (error_) {
|
|
143
|
+
const error = error_;
|
|
144
|
+
this.error(`Failed to get current branch: ${error.message}`, {
|
|
145
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
this.debug(`Current branch: ${currentBranch}`);
|
|
149
|
+
if (currentBranch === branchName) {
|
|
150
|
+
this.error(`Cannot delete branch '${branchName}' because you are currently on it.\n\n` +
|
|
151
|
+
`Please switch to a different directory first.\n\n` +
|
|
152
|
+
`Suggestion: 'aiw branch --main' has been copied to your clipboard.`, { exit: EXIT_CODES.INVALID_USAGE });
|
|
153
|
+
// Copy suggestion to clipboard
|
|
154
|
+
try {
|
|
155
|
+
await clipboardy.write('aiw branch --main');
|
|
156
|
+
this.debug('✓ Copied "aiw branch --main" to clipboard');
|
|
157
|
+
}
|
|
158
|
+
catch (clipboardError) {
|
|
159
|
+
this.debug('Failed to copy to clipboard:', clipboardError);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.debug(`✓ Not currently on branch '${branchName}'`);
|
|
164
|
+
// Check 5: Find the worktree path for this branch
|
|
165
|
+
this.debug(`Finding worktree path for branch '${branchName}'...`);
|
|
166
|
+
const worktreePath = getWorktreePath(branchName);
|
|
167
|
+
// Delete the worktree folder first if it exists (must be done before deleting the branch)
|
|
168
|
+
if (worktreePath) {
|
|
169
|
+
this.logInfo(`Deleting worktree folder at ${worktreePath}...`);
|
|
170
|
+
await deleteWorktreeFolder(worktreePath, { debugLog: (msg) => this.debug(msg) });
|
|
171
|
+
this.debug(`✓ Worktree folder deleted`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
this.debug('No worktree folder found for this branch');
|
|
175
|
+
}
|
|
176
|
+
// Delete the git branch
|
|
177
|
+
this.logInfo(`Deleting git branch '${branchName}'...`);
|
|
178
|
+
deleteBranch(branchName, { debugLog: (msg) => this.debug(msg) });
|
|
179
|
+
this.debug(`✓ Git branch '${branchName}' deleted`);
|
|
180
|
+
this.logSuccess(`✓ Branch '${branchName}' and its worktree have been deleted`);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
const err = error;
|
|
184
|
+
// Check if error is already handled by this.error() calls above
|
|
185
|
+
if (err.message?.includes('EEXIT')) {
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
// Handle unexpected errors
|
|
189
|
+
this.error(`Failed to delete branch: ${err.message}`, {
|
|
190
|
+
exit: EXIT_CODES.GENERAL_ERROR,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Handle --delete --all flags: Clean up all worktrees safely
|
|
196
|
+
*/
|
|
197
|
+
async handleDeleteAll() {
|
|
198
|
+
try {
|
|
199
|
+
const cwd = process.cwd();
|
|
200
|
+
// Check 1: Verify we are in a git repository
|
|
201
|
+
this.debug('Checking if current directory is a git repository...');
|
|
202
|
+
const isGitRepo = await this.isGitRepository(cwd);
|
|
203
|
+
if (!isGitRepo) {
|
|
204
|
+
this.error('Not a git repository. This command only works inside a git repository.', {
|
|
205
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
this.debug('✓ Git repository detected');
|
|
209
|
+
// Get all worktrees
|
|
210
|
+
this.logInfo('Scanning all worktrees in repository...');
|
|
211
|
+
const allWorktrees = getAllWorktrees();
|
|
212
|
+
this.debug(`Found ${allWorktrees.length} worktrees`);
|
|
213
|
+
// Track results
|
|
214
|
+
const deleted = [];
|
|
215
|
+
const preserved = [];
|
|
216
|
+
// Phase 1: Synchronous filtering
|
|
217
|
+
const candidatesForAsyncCheck = [];
|
|
218
|
+
const normalizedCwd = resolve(cwd);
|
|
219
|
+
for (const worktree of allWorktrees) {
|
|
220
|
+
const { branch, path } = worktree;
|
|
221
|
+
// Skip if no branch (detached HEAD)
|
|
222
|
+
if (!branch) {
|
|
223
|
+
this.debug(`Skipping worktree at ${path} (detached HEAD)`);
|
|
224
|
+
preserved.push({ branch, path, reason: 'detached HEAD' });
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
// Skip main/master branches
|
|
228
|
+
if (branch === 'main' || branch === 'master') {
|
|
229
|
+
this.debug(`Skipping protected branch: ${branch}`);
|
|
230
|
+
preserved.push({ branch, path, reason: 'protected branch (main/master)' });
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
// Check if this is the current directory
|
|
234
|
+
const normalizedPath = resolve(path);
|
|
235
|
+
if (normalizedCwd === normalizedPath) {
|
|
236
|
+
this.debug(`Skipping current directory worktree: ${path}`);
|
|
237
|
+
preserved.push({ branch, path, reason: 'current directory (cannot delete while inside)' });
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// Passed synchronous checks, add to async check candidates
|
|
241
|
+
candidatesForAsyncCheck.push({ branch, path });
|
|
242
|
+
}
|
|
243
|
+
// Phase 2: Parallel async safety checks (read-only operations)
|
|
244
|
+
this.debug(`Running safety checks on ${candidatesForAsyncCheck.length} candidates in parallel...`);
|
|
245
|
+
const debugLog = (msg) => this.debug(msg);
|
|
246
|
+
const safetyCheckResults = await Promise.all(candidatesForAsyncCheck.map(async ({ branch, path }) => {
|
|
247
|
+
this.debug(`Checking safety for branch '${branch}'...`);
|
|
248
|
+
if (hasUnpushedCommits(branch, { debugLog })) {
|
|
249
|
+
this.debug(`Branch '${branch}' has unpushed commits, skipping`);
|
|
250
|
+
return { branch, path, safe: false, reason: 'has unpushed commits' };
|
|
251
|
+
}
|
|
252
|
+
if (hasMergeRequest(branch, { debugLog })) {
|
|
253
|
+
this.debug(`Branch '${branch}' has an open PR, skipping`);
|
|
254
|
+
return { branch, path, safe: false, reason: 'has open pull request' };
|
|
255
|
+
}
|
|
256
|
+
this.debug(`Branch '${branch}' is safe to delete`);
|
|
257
|
+
return { branch, path, safe: true, reason: null };
|
|
258
|
+
}));
|
|
259
|
+
// Separate safe and unsafe candidates
|
|
260
|
+
const safeToDelete = safetyCheckResults.filter((r) => r.safe);
|
|
261
|
+
const unsafe = safetyCheckResults.filter((r) => !r.safe);
|
|
262
|
+
// Add unsafe candidates to preserved list
|
|
263
|
+
for (const { branch, path, reason } of unsafe) {
|
|
264
|
+
preserved.push({ branch, path, reason: reason });
|
|
265
|
+
}
|
|
266
|
+
// Phase 3: Sequential deletions (destructive operations must be sequential)
|
|
267
|
+
for (const { branch, path } of safeToDelete) {
|
|
268
|
+
try {
|
|
269
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential deletion required: worktree must be deleted before branch
|
|
270
|
+
await deleteWorktreeFolder(path, { debugLog });
|
|
271
|
+
deleteBranch(branch, { debugLog });
|
|
272
|
+
deleted.push({ branch, path });
|
|
273
|
+
this.debug(`✓ Deleted branch '${branch}' and worktree at ${path}`);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
const err = error;
|
|
277
|
+
this.debug(`Failed to delete branch '${branch}': ${err.message}`);
|
|
278
|
+
preserved.push({ branch, path, reason: `deletion failed: ${err.message}` });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Output results
|
|
282
|
+
this.log('');
|
|
283
|
+
this.logSuccess('✓ Worktree cleanup complete');
|
|
284
|
+
this.log('');
|
|
285
|
+
if (deleted.length > 0) {
|
|
286
|
+
this.logInfo(`Deleted ${deleted.length} worktree${deleted.length === 1 ? '' : 's'}:`);
|
|
287
|
+
for (const { branch, path } of deleted) {
|
|
288
|
+
this.log(` - ${branch} (${path})`);
|
|
289
|
+
}
|
|
290
|
+
this.log('');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
this.logInfo('No worktrees were deleted.');
|
|
294
|
+
this.log('');
|
|
295
|
+
}
|
|
296
|
+
if (preserved.length > 0) {
|
|
297
|
+
this.logInfo(`Preserved ${preserved.length} worktree${preserved.length === 1 ? '' : 's'}:`);
|
|
298
|
+
for (const { branch, path, reason } of preserved) {
|
|
299
|
+
this.log(` - ${branch ?? 'detached'} (${path})`);
|
|
300
|
+
this.log(` Reason: ${reason}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
const err = error;
|
|
306
|
+
// Check if error is already handled by this.error() calls above
|
|
307
|
+
if (err.message?.includes('EEXIT')) {
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
// Handle unexpected errors
|
|
311
|
+
this.error(`Failed to clean up worktrees: ${err.message}`, {
|
|
312
|
+
exit: EXIT_CODES.GENERAL_ERROR,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Handle --main flag: Launch aiw in main/master branch
|
|
318
|
+
*/
|
|
319
|
+
async handleMainBranch() {
|
|
320
|
+
try {
|
|
321
|
+
const cwd = process.cwd();
|
|
322
|
+
// Check 1: Verify we are in a git repository
|
|
323
|
+
this.debug('Checking if current directory is a git repository...');
|
|
324
|
+
const isGitRepo = await this.isGitRepository(cwd);
|
|
325
|
+
if (!isGitRepo) {
|
|
326
|
+
this.error('Not a git repository. This command only works inside a git repository.', {
|
|
327
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
this.debug('✓ Git repository detected');
|
|
331
|
+
// Check 2: Get current branch
|
|
332
|
+
this.debug('Getting current branch name...');
|
|
333
|
+
let currentBranch;
|
|
334
|
+
try {
|
|
335
|
+
currentBranch = getCurrentBranch();
|
|
336
|
+
}
|
|
337
|
+
catch (error_) {
|
|
338
|
+
const error = error_;
|
|
339
|
+
this.error(`Failed to get current branch: ${error.message}`, {
|
|
340
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
this.debug(`Current branch: ${currentBranch}`);
|
|
344
|
+
// Check 3: Verify we are NOT on main/master
|
|
345
|
+
if (currentBranch === 'main' || currentBranch === 'master') {
|
|
346
|
+
this.error(`Already on ${currentBranch} branch. This command is for switching to main/master from another branch.`, {
|
|
347
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
this.debug('✓ Currently on a feature branch');
|
|
351
|
+
// Check 4: Determine which main branch exists (main or master)
|
|
352
|
+
this.debug('Checking which main branch exists...');
|
|
353
|
+
const mainBranch = getMainBranch();
|
|
354
|
+
if (!mainBranch) {
|
|
355
|
+
this.error('Neither "main" nor "master" branch exists in this repository.', {
|
|
356
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
this.debug(`✓ Found main branch: ${mainBranch}`);
|
|
360
|
+
// Get the worktree path for the main branch
|
|
361
|
+
this.debug(`Finding worktree path for ${mainBranch} branch...`);
|
|
362
|
+
const mainBranchPath = getWorktreePath(mainBranch);
|
|
363
|
+
if (!mainBranchPath) {
|
|
364
|
+
this.error(`Could not find worktree for ${mainBranch} branch.`, {
|
|
365
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
this.debug(`✓ Found ${mainBranch} worktree at: ${mainBranchPath}`);
|
|
369
|
+
// Launch new terminal with aiw launch in main/master branch
|
|
370
|
+
this.logInfo(`Opening new terminal with aiw launch in ${mainBranch} branch...`);
|
|
371
|
+
const result = await launchTerminal({
|
|
372
|
+
cwd: mainBranchPath,
|
|
373
|
+
command: 'aiw launch',
|
|
374
|
+
debugLog: (msg) => this.debug(msg),
|
|
375
|
+
});
|
|
376
|
+
if (!result.success) {
|
|
377
|
+
this.error(`Failed to launch terminal: ${result.error}`, { exit: EXIT_CODES.GENERAL_ERROR });
|
|
378
|
+
}
|
|
379
|
+
this.logSuccess(`✓ New terminal launched with aiw in ${mainBranch} branch`);
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
const err = error;
|
|
383
|
+
// Check if error is already handled by this.error() calls above
|
|
384
|
+
if (err.message?.includes('EEXIT')) {
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
// Handle unexpected errors
|
|
388
|
+
this.error(`Failed to launch terminal: ${err.message}`, {
|
|
389
|
+
exit: EXIT_CODES.GENERAL_ERROR,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Handle --launch flag: Create or open worktree in sibling folder
|
|
395
|
+
*/
|
|
396
|
+
async handleWorktreeLaunch(branchName) {
|
|
397
|
+
// Validate branch name
|
|
398
|
+
if (!branchName || branchName.trim().length === 0) {
|
|
399
|
+
this.error('Branch name is required when using --launch flag', { exit: EXIT_CODES.INVALID_USAGE });
|
|
400
|
+
}
|
|
401
|
+
// Validate branch name format (no spaces, special chars that could cause issues)
|
|
402
|
+
const validBranchNamePattern = /^[a-zA-Z0-9._/-]+$/;
|
|
403
|
+
if (!validBranchNamePattern.test(branchName)) {
|
|
404
|
+
this.error('Branch name contains invalid characters. Use only letters, numbers, dots, dashes, underscores, and slashes.', { exit: EXIT_CODES.INVALID_USAGE });
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
const cwd = process.cwd();
|
|
408
|
+
// Check if we're in a git repository
|
|
409
|
+
const isGitRepo = await this.isGitRepository(cwd);
|
|
410
|
+
if (!isGitRepo) {
|
|
411
|
+
this.error('Not a git repository. This command only works inside a git repository.', {
|
|
412
|
+
exit: EXIT_CODES.ENVIRONMENT_ERROR,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// Get current directory and derive sibling folder name
|
|
416
|
+
const currentDirName = basename(cwd);
|
|
417
|
+
const parentDir = dirname(cwd);
|
|
418
|
+
const worktreeFolderName = `${currentDirName}-${branchName}`;
|
|
419
|
+
const worktreePath = resolve(parentDir, worktreeFolderName);
|
|
420
|
+
this.debug(`Checking for existing worktree at: ${worktreePath}`);
|
|
421
|
+
// Check if worktree folder already exists
|
|
422
|
+
let worktreeExists = false;
|
|
423
|
+
try {
|
|
424
|
+
await fs.access(worktreePath);
|
|
425
|
+
worktreeExists = true;
|
|
426
|
+
this.logInfo(`Worktree already exists at: ${worktreePath}`);
|
|
427
|
+
this.logInfo(`Opening terminal in existing worktree...`);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
// Folder doesn't exist, we'll create it
|
|
431
|
+
this.logInfo(`Creating worktree for branch: ${branchName}`);
|
|
432
|
+
this.logInfo(`Worktree location: ${worktreePath}`);
|
|
433
|
+
}
|
|
434
|
+
if (!worktreeExists) {
|
|
435
|
+
// Create git worktree
|
|
436
|
+
await createWorktree(branchName, worktreePath);
|
|
437
|
+
this.logSuccess(`✓ Created worktree at ${worktreePath}`);
|
|
438
|
+
this.logSuccess(`✓ Created and checked out branch: ${branchName}`);
|
|
439
|
+
}
|
|
440
|
+
// Launch terminal in worktree path
|
|
441
|
+
const result = await launchTerminal({
|
|
442
|
+
cwd: worktreePath,
|
|
443
|
+
command: 'aiw launch',
|
|
444
|
+
debugLog: (msg) => this.debug(msg),
|
|
445
|
+
});
|
|
446
|
+
if (!result.success) {
|
|
447
|
+
this.error(`Failed to launch terminal: ${result.error}`, { exit: EXIT_CODES.GENERAL_ERROR });
|
|
448
|
+
}
|
|
449
|
+
this.logSuccess('✓ Launched terminal with aiw launch');
|
|
450
|
+
this.log('');
|
|
451
|
+
this.logInfo('New terminal window opened at worktree location.');
|
|
452
|
+
this.logInfo('Claude Code should be launching automatically.');
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
const err = error;
|
|
456
|
+
// Check if error is already handled by this.error() calls above
|
|
457
|
+
if (err.message?.includes('EEXIT')) {
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
// Handle git-specific errors
|
|
461
|
+
if (err.message?.includes('already exists')) {
|
|
462
|
+
this.error(`Branch '${branchName}' already exists. Choose a different name.`, {
|
|
463
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (err.stderr?.includes('fatal: not a git repository')) {
|
|
467
|
+
this.error('Not a git repository. Please run this command from a git repository root.', {
|
|
468
|
+
exit: EXIT_CODES.INVALID_USAGE,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
// Generic error fallback
|
|
472
|
+
this.error(`Failed to create/open worktree: ${err.message}`, { exit: EXIT_CODES.GENERAL_ERROR });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Check if current directory is a git repository
|
|
477
|
+
*/
|
|
478
|
+
async isGitRepository(cwd) {
|
|
479
|
+
try {
|
|
480
|
+
const gitPath = join(cwd, '.git');
|
|
481
|
+
await fs.access(gitPath);
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import BaseCommand from '../lib/base-command.js';
|
|
2
|
+
/**
|
|
3
|
+
* Clean output folder(s) for methods.
|
|
4
|
+
* Removes all files and subdirectories from the method's output folder.
|
|
5
|
+
*/
|
|
6
|
+
export default class CleanCommand extends BaseCommand {
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
method: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
debug: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
help: import("@oclif/core/interfaces").BooleanFlag<void>;
|
|
16
|
+
quiet: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Find all output folders in the target directory.
|
|
21
|
+
* Matches folders with pattern _*-output (e.g., _bmad-output, _gsd-output).
|
|
22
|
+
*
|
|
23
|
+
* @param targetDir - Directory to search in
|
|
24
|
+
* @returns Array of output folder names
|
|
25
|
+
*/
|
|
26
|
+
private findAllOutputFolders;
|
|
27
|
+
/**
|
|
28
|
+
* Get all top-level contents of a directory.
|
|
29
|
+
*
|
|
30
|
+
* @param dirPath - Directory to list contents of
|
|
31
|
+
* @returns Array of content items with path and type info
|
|
32
|
+
*/
|
|
33
|
+
private getContents;
|
|
34
|
+
}
|