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.
Files changed (204) hide show
  1. package/README.md +1248 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +16 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +19 -0
  6. package/dist/commands/branch.d.ts +45 -0
  7. package/dist/commands/branch.js +488 -0
  8. package/dist/commands/clean.d.ts +34 -0
  9. package/dist/commands/clean.js +186 -0
  10. package/dist/commands/clear.d.ts +51 -0
  11. package/dist/commands/clear.js +835 -0
  12. package/dist/commands/init/index.d.ts +107 -0
  13. package/dist/commands/init/index.js +565 -0
  14. package/dist/commands/launch.d.ts +21 -0
  15. package/dist/commands/launch.js +108 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/base-command.d.ts +114 -0
  19. package/dist/lib/base-command.js +153 -0
  20. package/dist/lib/bmad-installer.d.ts +38 -0
  21. package/dist/lib/bmad-installer.js +145 -0
  22. package/dist/lib/claude-settings-types.d.ts +102 -0
  23. package/dist/lib/claude-settings-types.js +5 -0
  24. package/dist/lib/config.d.ts +25 -0
  25. package/dist/lib/config.js +46 -0
  26. package/dist/lib/debug.d.ts +39 -0
  27. package/dist/lib/debug.js +74 -0
  28. package/dist/lib/env-compat.d.ts +26 -0
  29. package/dist/lib/env-compat.js +35 -0
  30. package/dist/lib/errors.d.ts +126 -0
  31. package/dist/lib/errors.js +145 -0
  32. package/dist/lib/generic-merge.d.ts +74 -0
  33. package/dist/lib/generic-merge.js +105 -0
  34. package/dist/lib/git/branch.d.ts +67 -0
  35. package/dist/lib/git/branch.js +155 -0
  36. package/dist/lib/git/index.d.ts +11 -0
  37. package/dist/lib/git/index.js +13 -0
  38. package/dist/lib/git/safety-checks.d.ts +44 -0
  39. package/dist/lib/git/safety-checks.js +102 -0
  40. package/dist/lib/git/types.d.ts +31 -0
  41. package/dist/lib/git/types.js +6 -0
  42. package/dist/lib/git/worktree.d.ts +67 -0
  43. package/dist/lib/git/worktree.js +220 -0
  44. package/dist/lib/gitignore-manager.d.ts +10 -0
  45. package/dist/lib/gitignore-manager.js +60 -0
  46. package/dist/lib/hooks-merger.d.ts +28 -0
  47. package/dist/lib/hooks-merger.js +94 -0
  48. package/dist/lib/ide-path-resolver.d.ts +102 -0
  49. package/dist/lib/ide-path-resolver.js +129 -0
  50. package/dist/lib/index.d.ts +13 -0
  51. package/dist/lib/index.js +22 -0
  52. package/dist/lib/output.d.ts +51 -0
  53. package/dist/lib/output.js +76 -0
  54. package/dist/lib/paths.d.ts +66 -0
  55. package/dist/lib/paths.js +136 -0
  56. package/dist/lib/quiet.d.ts +12 -0
  57. package/dist/lib/quiet.js +17 -0
  58. package/dist/lib/settings-hierarchy.d.ts +42 -0
  59. package/dist/lib/settings-hierarchy.js +105 -0
  60. package/dist/lib/spawn.d.ts +105 -0
  61. package/dist/lib/spawn.js +157 -0
  62. package/dist/lib/spinner.d.ts +19 -0
  63. package/dist/lib/spinner.js +34 -0
  64. package/dist/lib/stdin.d.ts +48 -0
  65. package/dist/lib/stdin.js +60 -0
  66. package/dist/lib/template-installer.d.ts +92 -0
  67. package/dist/lib/template-installer.js +375 -0
  68. package/dist/lib/template-linter.d.ts +49 -0
  69. package/dist/lib/template-linter.js +173 -0
  70. package/dist/lib/template-merger.d.ts +47 -0
  71. package/dist/lib/template-merger.js +173 -0
  72. package/dist/lib/template-resolver.d.ts +20 -0
  73. package/dist/lib/template-resolver.js +60 -0
  74. package/dist/lib/terminal.d.ts +102 -0
  75. package/dist/lib/terminal.js +245 -0
  76. package/dist/lib/tty-detection.d.ts +62 -0
  77. package/dist/lib/tty-detection.js +83 -0
  78. package/dist/lib/user-utils.d.ts +5 -0
  79. package/dist/lib/user-utils.js +23 -0
  80. package/dist/lib/version.d.ts +99 -0
  81. package/dist/lib/version.js +144 -0
  82. package/dist/lib/watch-templates.d.ts +6 -0
  83. package/dist/lib/watch-templates.js +73 -0
  84. package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
  85. package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
  86. package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
  87. package/dist/lib/windsurf-hooks-merger.js +53 -0
  88. package/dist/lib/windsurf-hooks-types.d.ts +33 -0
  89. package/dist/lib/windsurf-hooks-types.js +5 -0
  90. package/dist/templates/CLAUDE.md +174 -0
  91. package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
  92. package/dist/templates/_shared/.claude/settings.json +61 -0
  93. package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
  94. package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
  95. package/dist/templates/_shared/hooks/__init__.py +16 -0
  96. package/dist/templates/_shared/hooks/archive_plan.py +270 -0
  97. package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
  98. package/dist/templates/_shared/hooks/context_monitor.py +322 -0
  99. package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
  100. package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
  101. package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
  102. package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
  103. package/dist/templates/_shared/lib/__init__.py +1 -0
  104. package/dist/templates/_shared/lib/base/__init__.py +49 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
  107. package/dist/templates/_shared/lib/base/constants.py +299 -0
  108. package/dist/templates/_shared/lib/base/inference.py +189 -0
  109. package/dist/templates/_shared/lib/base/utils.py +216 -0
  110. package/dist/templates/_shared/lib/context/__init__.py +119 -0
  111. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  112. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  113. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  114. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  115. package/dist/templates/_shared/lib/context/cache.py +446 -0
  116. package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
  117. package/dist/templates/_shared/lib/context/discovery.py +486 -0
  118. package/dist/templates/_shared/lib/context/event_log.py +308 -0
  119. package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
  120. package/dist/templates/_shared/lib/context/task_sync.py +367 -0
  121. package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
  122. package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
  123. package/dist/templates/_shared/lib/templates/README.md +215 -0
  124. package/dist/templates/_shared/lib/templates/__init__.py +40 -0
  125. package/dist/templates/_shared/lib/templates/formatters.py +147 -0
  126. package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
  127. package/dist/templates/_shared/scripts/save_handoff.py +99 -0
  128. package/dist/templates/_shared/workflows/handoff.md +212 -0
  129. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
  130. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
  131. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
  132. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
  133. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
  134. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
  135. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
  136. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
  137. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
  138. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
  139. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
  140. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
  141. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
  142. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
  143. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
  144. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
  145. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
  146. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
  147. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
  148. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
  149. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
  150. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
  151. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
  152. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
  153. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
  154. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
  155. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
  156. package/dist/templates/cc-native/.claude/settings.json +119 -0
  157. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
  158. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
  159. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
  160. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
  161. package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
  162. package/dist/templates/cc-native/MIGRATION.md +86 -0
  163. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
  164. package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
  165. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
  171. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
  172. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
  174. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  175. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  176. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
  179. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
  180. package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
  181. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  187. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
  189. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
  190. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
  191. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
  192. package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
  193. package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
  194. package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
  195. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  196. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
  197. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
  198. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
  199. package/dist/types/exit-codes.d.ts +11 -0
  200. package/dist/types/exit-codes.js +10 -0
  201. package/dist/types/index.d.ts +5 -0
  202. package/dist/types/index.js +7 -0
  203. package/oclif.manifest.json +405 -0
  204. package/package.json +109 -0
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file Generic merge utilities for configuration merging.
3
+ *
4
+ * This module provides reusable utilities for merging arrays with deduplication
5
+ * and merging configuration objects by event type. Used by hook merger modules
6
+ * to eliminate code duplication.
7
+ *
8
+ * ## Usage
9
+ * ```typescript
10
+ * import { mergeArraysWithDedup, mergeConfigByEventType } from '../lib/generic-merge.js'
11
+ *
12
+ * // Merge arrays with custom equality
13
+ * const merged = mergeArraysWithDedup(existing, template, (a, b) => a.id === b.id)
14
+ *
15
+ * // Merge config objects by event type
16
+ * const mergedConfig = mergeConfigByEventType(
17
+ * existingConfig,
18
+ * templateConfig,
19
+ * (existing, template) => mergeArraysWithDedup(existing, template, areEqual)
20
+ * )
21
+ * ```
22
+ *
23
+ * @module lib/generic-merge
24
+ */
25
+ /**
26
+ * Merge two arrays with deduplication based on custom equality function.
27
+ *
28
+ * Items from the template array are appended to the existing array only if
29
+ * no equivalent item exists. Order is preserved: existing items first.
30
+ *
31
+ * @template T - Type of array elements
32
+ * @param existing - Existing array (preserved in order)
33
+ * @param template - Template array to merge (items appended if not duplicate)
34
+ * @param areEqual - Function to check if two items are equivalent
35
+ * @returns New merged array with duplicates removed
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const merged = mergeArraysWithDedup(
40
+ * [{id: 1, name: 'a'}],
41
+ * [{id: 1, name: 'b'}, {id: 2, name: 'c'}],
42
+ * (a, b) => a.id === b.id
43
+ * )
44
+ * // Result: [{id: 1, name: 'a'}, {id: 2, name: 'c'}]
45
+ * ```
46
+ */
47
+ export function mergeArraysWithDedup(existing, template, areEqual) {
48
+ const merged = [...existing];
49
+ for (const templateItem of template) {
50
+ const isDuplicate = merged.some((existingItem) => areEqual(existingItem, templateItem));
51
+ if (!isDuplicate) {
52
+ merged.push(templateItem);
53
+ }
54
+ }
55
+ return merged;
56
+ }
57
+ /**
58
+ * Merge configuration objects organized by event type.
59
+ *
60
+ * Each configuration maps event types to arrays of items. This function
61
+ * merges configurations by:
62
+ * 1. Collecting all event types from both configurations
63
+ * 2. For each event type, merging the arrays using the provided merge function
64
+ *
65
+ * @template TEventType - Type of event type keys (typically string union)
66
+ * @template TItem - Type of items in the arrays
67
+ * @template TConfig - Type of the configuration object
68
+ * @param existing - Existing configuration (may be undefined)
69
+ * @param template - Template configuration to merge (may be undefined)
70
+ * @param mergeArrays - Function to merge arrays for a single event type
71
+ * @returns New merged configuration
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * const mergedConfig = mergeConfigByEventType(
76
+ * {click: [handler1], hover: [handler2]},
77
+ * {click: [handler3], focus: [handler4]},
78
+ * (existing, template) => mergeArraysWithDedup(existing, template, areEqual)
79
+ * )
80
+ * // Result: {click: [handler1, handler3], hover: [handler2], focus: [handler4]}
81
+ * ```
82
+ */
83
+ export function mergeConfigByEventType(existing, template, mergeArrays) {
84
+ // If no template, return existing (or empty object)
85
+ if (!template || Object.keys(template).length === 0) {
86
+ return (existing || {});
87
+ }
88
+ // If no existing, return template
89
+ if (!existing || Object.keys(existing).length === 0) {
90
+ return template;
91
+ }
92
+ const merged = {};
93
+ // Get all unique event types from both configurations
94
+ const allEventTypes = new Set([
95
+ ...Object.keys(existing),
96
+ ...Object.keys(template),
97
+ ]);
98
+ // Merge each event type
99
+ for (const eventType of allEventTypes) {
100
+ const existingItems = existing[eventType] || [];
101
+ const templateItems = template[eventType] || [];
102
+ merged[eventType] = mergeArrays(existingItems, templateItems);
103
+ }
104
+ return merged;
105
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @file Git branch operations.
3
+ *
4
+ * Provides utilities for checking, getting, and deleting git branches.
5
+ *
6
+ * @module lib/git/branch
7
+ */
8
+ import type { GitCommandOptions } from './types.js';
9
+ /**
10
+ * Check if a git branch exists (local).
11
+ *
12
+ * @param branchName - Name of the branch to check
13
+ * @returns True if branch exists locally
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * if (await branchExists('feature-branch')) {
18
+ * console.log('Branch exists')
19
+ * }
20
+ * ```
21
+ */
22
+ export declare function branchExists(branchName: string): boolean;
23
+ /**
24
+ * Get the current git branch name.
25
+ *
26
+ * @returns Current branch name
27
+ * @throws Error if not in a git repository or unable to determine branch
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const branch = getCurrentBranch()
32
+ * console.log(`Currently on: ${branch}`)
33
+ * ```
34
+ */
35
+ export declare function getCurrentBranch(): string;
36
+ /**
37
+ * Determine which main branch exists (main or master).
38
+ *
39
+ * Returns 'main' if it exists (preferred), 'master' if it exists,
40
+ * or null if neither exists.
41
+ *
42
+ * @returns 'main', 'master', or null
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const mainBranch = getMainBranch()
47
+ * if (mainBranch) {
48
+ * console.log(`Main branch: ${mainBranch}`)
49
+ * }
50
+ * ```
51
+ */
52
+ export declare function getMainBranch(): null | string;
53
+ /**
54
+ * Delete a git branch (local and remote if exists).
55
+ *
56
+ * @param branchName - Name of the branch to delete
57
+ * @param options - Command options including debug logging
58
+ * @throws Error if unable to delete branch
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * await deleteBranch('feature-branch', {
63
+ * debugLog: (msg) => console.debug(msg)
64
+ * })
65
+ * ```
66
+ */
67
+ export declare function deleteBranch(branchName: string, options?: GitCommandOptions): void;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @file Git branch operations.
3
+ *
4
+ * Provides utilities for checking, getting, and deleting git branches.
5
+ *
6
+ * @module lib/git/branch
7
+ */
8
+ import { execSync } from 'node:child_process';
9
+ /**
10
+ * Check if a git branch exists (local).
11
+ *
12
+ * @param branchName - Name of the branch to check
13
+ * @returns True if branch exists locally
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * if (await branchExists('feature-branch')) {
18
+ * console.log('Branch exists')
19
+ * }
20
+ * ```
21
+ */
22
+ export function branchExists(branchName) {
23
+ try {
24
+ execSync(`git show-ref --verify refs/heads/${branchName}`, {
25
+ encoding: 'utf8',
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ });
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ /**
35
+ * Get the current git branch name.
36
+ *
37
+ * @returns Current branch name
38
+ * @throws Error if not in a git repository or unable to determine branch
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const branch = getCurrentBranch()
43
+ * console.log(`Currently on: ${branch}`)
44
+ * ```
45
+ */
46
+ export function getCurrentBranch() {
47
+ try {
48
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
49
+ encoding: 'utf8',
50
+ stdio: ['pipe', 'pipe', 'pipe'],
51
+ }).trim();
52
+ return branch;
53
+ }
54
+ catch (error) {
55
+ const err = error;
56
+ throw new Error(`Failed to get current branch: ${err.message}`);
57
+ }
58
+ }
59
+ /**
60
+ * Determine which main branch exists (main or master).
61
+ *
62
+ * Returns 'main' if it exists (preferred), 'master' if it exists,
63
+ * or null if neither exists.
64
+ *
65
+ * @returns 'main', 'master', or null
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const mainBranch = getMainBranch()
70
+ * if (mainBranch) {
71
+ * console.log(`Main branch: ${mainBranch}`)
72
+ * }
73
+ * ```
74
+ */
75
+ export function getMainBranch() {
76
+ // Check for 'main' first (more modern convention)
77
+ try {
78
+ execSync('git show-ref --verify refs/heads/main', {
79
+ encoding: 'utf8',
80
+ stdio: ['pipe', 'pipe', 'pipe'],
81
+ });
82
+ return 'main';
83
+ }
84
+ catch {
85
+ // main doesn't exist, try master
86
+ }
87
+ // Check for 'master'
88
+ try {
89
+ execSync('git show-ref --verify refs/heads/master', {
90
+ encoding: 'utf8',
91
+ stdio: ['pipe', 'pipe', 'pipe'],
92
+ });
93
+ return 'master';
94
+ }
95
+ catch {
96
+ // neither exists
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Delete a git branch (local and remote if exists).
102
+ *
103
+ * @param branchName - Name of the branch to delete
104
+ * @param options - Command options including debug logging
105
+ * @throws Error if unable to delete branch
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * await deleteBranch('feature-branch', {
110
+ * debugLog: (msg) => console.debug(msg)
111
+ * })
112
+ * ```
113
+ */
114
+ export function deleteBranch(branchName, options) {
115
+ const { debugLog } = options || {};
116
+ // Platform-specific branch name escaping
117
+ const escapedBranch = process.platform === 'win32'
118
+ ? `"${branchName.replaceAll('"', String.raw `\"`)}"` // Windows: double quotes
119
+ : `'${branchName.replaceAll('\'', String.raw `'\''`)}'`; // Unix/macOS: single quotes
120
+ // Delete local branch
121
+ debugLog?.(`Deleting local branch '${branchName}'...`);
122
+ try {
123
+ execSync(`git branch -D ${escapedBranch}`, {
124
+ encoding: 'utf8',
125
+ stdio: ['pipe', 'pipe', 'pipe'],
126
+ });
127
+ }
128
+ catch (error) {
129
+ const err = error;
130
+ // If branch doesn't exist (orphaned worktree), that's fine - just log it
131
+ if (err.message?.includes('not found')) {
132
+ debugLog?.(`Branch '${branchName}' not found (orphaned worktree)`);
133
+ return;
134
+ }
135
+ // For other errors, throw
136
+ throw new Error(`Failed to delete branch: ${err.message}`);
137
+ }
138
+ // Check if remote branch exists
139
+ try {
140
+ execSync(`git show-ref --verify refs/remotes/origin/${branchName}`, {
141
+ encoding: 'utf8',
142
+ stdio: ['pipe', 'pipe', 'pipe'],
143
+ });
144
+ // Remote branch exists, delete it
145
+ debugLog?.(`Deleting remote branch '${branchName}'...`);
146
+ execSync(`git push origin --delete ${escapedBranch}`, {
147
+ encoding: 'utf8',
148
+ stdio: ['pipe', 'pipe', 'pipe'],
149
+ });
150
+ }
151
+ catch {
152
+ // Remote branch doesn't exist, skip deletion
153
+ debugLog?.('No remote branch to delete');
154
+ }
155
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @file Git utilities module.
3
+ *
4
+ * Provides reusable git operations for branch, worktree, and safety checks.
5
+ *
6
+ * @module lib/git
7
+ */
8
+ export { branchExists, deleteBranch, getCurrentBranch, getMainBranch } from './branch.js';
9
+ export { hasMergeRequest, hasUnpushedCommits } from './safety-checks.js';
10
+ export type { GitCommandOptions, WorktreeInfo } from './types.js';
11
+ export { createWorktree, deleteWorktreeFolder, getAllWorktrees, getWorktreePath } from './worktree.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file Git utilities module.
3
+ *
4
+ * Provides reusable git operations for branch, worktree, and safety checks.
5
+ *
6
+ * @module lib/git
7
+ */
8
+ // Branch operations
9
+ export { branchExists, deleteBranch, getCurrentBranch, getMainBranch } from './branch.js';
10
+ // Safety checks
11
+ export { hasMergeRequest, hasUnpushedCommits } from './safety-checks.js';
12
+ // Worktree operations
13
+ export { createWorktree, deleteWorktreeFolder, getAllWorktrees, getWorktreePath } from './worktree.js';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @file Git safety check operations.
3
+ *
4
+ * Provides utilities for checking if branches are safe to delete.
5
+ *
6
+ * @module lib/git/safety-checks
7
+ */
8
+ import type { GitCommandOptions } from './types.js';
9
+ /**
10
+ * Check if a branch has unpushed commits.
11
+ *
12
+ * Returns true if there are commits not pushed to remote, or if
13
+ * the branch has no remote tracking branch (safer assumption).
14
+ *
15
+ * @param branchName - Name of the branch to check
16
+ * @param options - Command options including debug logging
17
+ * @returns True if there are unpushed commits
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * if (hasUnpushedCommits('feature-branch')) {
22
+ * console.log('Branch has unpushed commits, cannot delete')
23
+ * }
24
+ * ```
25
+ */
26
+ export declare function hasUnpushedCommits(branchName: string, options?: GitCommandOptions): boolean;
27
+ /**
28
+ * Check if a branch has an open merge request/pull request.
29
+ *
30
+ * Uses GitHub CLI (gh) to check for open PRs. Returns false if gh is
31
+ * not available or if unable to check.
32
+ *
33
+ * @param branchName - Name of the branch to check
34
+ * @param options - Command options including debug logging
35
+ * @returns True if an open PR exists for this branch
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * if (hasMergeRequest('feature-branch')) {
40
+ * console.log('Branch has an open PR, cannot delete')
41
+ * }
42
+ * ```
43
+ */
44
+ export declare function hasMergeRequest(branchName: string, options?: GitCommandOptions): boolean;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @file Git safety check operations.
3
+ *
4
+ * Provides utilities for checking if branches are safe to delete.
5
+ *
6
+ * @module lib/git/safety-checks
7
+ */
8
+ import { execSync } from 'node:child_process';
9
+ /**
10
+ * Check if a branch has unpushed commits.
11
+ *
12
+ * Returns true if there are commits not pushed to remote, or if
13
+ * the branch has no remote tracking branch (safer assumption).
14
+ *
15
+ * @param branchName - Name of the branch to check
16
+ * @param options - Command options including debug logging
17
+ * @returns True if there are unpushed commits
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * if (hasUnpushedCommits('feature-branch')) {
22
+ * console.log('Branch has unpushed commits, cannot delete')
23
+ * }
24
+ * ```
25
+ */
26
+ export function hasUnpushedCommits(branchName, options) {
27
+ const { debugLog } = options || {};
28
+ try {
29
+ // Check if remote branch exists first
30
+ const escapedBranch = branchName.replaceAll('\'', String.raw `'\''`);
31
+ try {
32
+ execSync(`git show-ref --verify refs/remotes/origin/${escapedBranch}`, {
33
+ encoding: 'utf8',
34
+ stdio: ['pipe', 'pipe', 'pipe'],
35
+ });
36
+ }
37
+ catch {
38
+ // No remote branch - treat as having unpushed commits (safer)
39
+ debugLog?.(`Branch '${branchName}' has no remote tracking branch`);
40
+ return true;
41
+ }
42
+ // Check if there are commits ahead of remote
43
+ const output = execSync(`git rev-list origin/${escapedBranch}..${escapedBranch} --count`, {
44
+ encoding: 'utf8',
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ }).trim();
47
+ const commitCount = Number.parseInt(output, 10);
48
+ return commitCount > 0;
49
+ }
50
+ catch (error) {
51
+ // If we can't determine, err on the side of caution
52
+ debugLog?.(`Error checking unpushed commits for '${branchName}': ${error}`);
53
+ return true;
54
+ }
55
+ }
56
+ /**
57
+ * Check if a branch has an open merge request/pull request.
58
+ *
59
+ * Uses GitHub CLI (gh) to check for open PRs. Returns false if gh is
60
+ * not available or if unable to check.
61
+ *
62
+ * @param branchName - Name of the branch to check
63
+ * @param options - Command options including debug logging
64
+ * @returns True if an open PR exists for this branch
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * if (hasMergeRequest('feature-branch')) {
69
+ * console.log('Branch has an open PR, cannot delete')
70
+ * }
71
+ * ```
72
+ */
73
+ export function hasMergeRequest(branchName, options) {
74
+ const { debugLog } = options || {};
75
+ try {
76
+ // Check if gh CLI is available
77
+ try {
78
+ execSync('gh --version', {
79
+ stdio: ['pipe', 'pipe', 'pipe'],
80
+ });
81
+ }
82
+ catch {
83
+ // gh CLI not available, can't check for PRs
84
+ debugLog?.('gh CLI not available, skipping PR check');
85
+ return false;
86
+ }
87
+ // Check for open PRs for this branch
88
+ const escapedBranch = branchName.replaceAll('\'', String.raw `'\''`);
89
+ const output = execSync(`gh pr list --head '${escapedBranch}' --state open --json number`, {
90
+ encoding: 'utf8',
91
+ stdio: ['pipe', 'pipe', 'pipe'],
92
+ }).trim();
93
+ // Parse JSON output
94
+ const prs = JSON.parse(output);
95
+ return prs.length > 0;
96
+ }
97
+ catch (error) {
98
+ // If we can't determine, assume no PR (less conservative than unpushed commits)
99
+ debugLog?.(`Error checking for PR on branch '${branchName}': ${error}`);
100
+ return false;
101
+ }
102
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @file Git-related type definitions.
3
+ *
4
+ * @module lib/git/types
5
+ */
6
+ /**
7
+ * Information about a git worktree.
8
+ */
9
+ export interface WorktreeInfo {
10
+ /**
11
+ * Branch name, or null if detached HEAD.
12
+ */
13
+ branch: null | string;
14
+ /**
15
+ * Commit hash of the worktree HEAD.
16
+ */
17
+ head: string;
18
+ /**
19
+ * Absolute path to the worktree directory.
20
+ */
21
+ path: string;
22
+ }
23
+ /**
24
+ * Options for git command execution.
25
+ */
26
+ export interface GitCommandOptions {
27
+ /**
28
+ * Optional debug logging function.
29
+ */
30
+ debugLog?: (message: string) => void;
31
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @file Git-related type definitions.
3
+ *
4
+ * @module lib/git/types
5
+ */
6
+ export {};
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @file Git worktree operations.
3
+ *
4
+ * Provides utilities for managing git worktrees.
5
+ *
6
+ * @module lib/git/worktree
7
+ */
8
+ import type { GitCommandOptions, WorktreeInfo } from './types.js';
9
+ /**
10
+ * Get all worktrees in the repository.
11
+ *
12
+ * @returns Array of worktree info objects
13
+ * @throws Error if unable to list worktrees
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const worktrees = getAllWorktrees()
18
+ * for (const wt of worktrees) {
19
+ * console.log(`${wt.branch}: ${wt.path}`)
20
+ * }
21
+ * ```
22
+ */
23
+ export declare function getAllWorktrees(): WorktreeInfo[];
24
+ /**
25
+ * Get the worktree path for a branch.
26
+ *
27
+ * @param branchName - Name of the branch
28
+ * @returns Worktree path if found, null otherwise
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const path = getWorktreePath('main')
33
+ * if (path) {
34
+ * console.log(`Main branch worktree: ${path}`)
35
+ * }
36
+ * ```
37
+ */
38
+ export declare function getWorktreePath(branchName: string): null | string;
39
+ /**
40
+ * Create a git worktree with the specified branch name.
41
+ *
42
+ * @param branchName - Name of the branch to create
43
+ * @param worktreePath - Path where the worktree should be created
44
+ * @returns Promise that resolves when worktree is created
45
+ * @throws Error if unable to create worktree
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * await createWorktree('feature-branch', '/path/to/worktree')
50
+ * ```
51
+ */
52
+ export declare function createWorktree(branchName: string, worktreePath: string): Promise<void>;
53
+ /**
54
+ * Delete a worktree folder and remove worktree from git.
55
+ *
56
+ * @param worktreePath - Path to the worktree to delete
57
+ * @param options - Command options including debug logging
58
+ * @throws Error if unable to delete worktree folder
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * await deleteWorktreeFolder('/path/to/worktree', {
63
+ * debugLog: (msg) => console.debug(msg)
64
+ * })
65
+ * ```
66
+ */
67
+ export declare function deleteWorktreeFolder(worktreePath: string, options?: GitCommandOptions): Promise<void>;