@lumenflow/core 1.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.
Files changed (263) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +119 -0
  3. package/dist/active-wu-detector.d.ts +33 -0
  4. package/dist/active-wu-detector.js +106 -0
  5. package/dist/adapters/filesystem-metrics.adapter.d.ts +108 -0
  6. package/dist/adapters/filesystem-metrics.adapter.js +519 -0
  7. package/dist/adapters/terminal-renderer.adapter.d.ts +106 -0
  8. package/dist/adapters/terminal-renderer.adapter.js +337 -0
  9. package/dist/arg-parser.d.ts +63 -0
  10. package/dist/arg-parser.js +560 -0
  11. package/dist/backlog-editor.d.ts +98 -0
  12. package/dist/backlog-editor.js +179 -0
  13. package/dist/backlog-generator.d.ts +111 -0
  14. package/dist/backlog-generator.js +381 -0
  15. package/dist/backlog-parser.d.ts +45 -0
  16. package/dist/backlog-parser.js +102 -0
  17. package/dist/backlog-sync-validator.d.ts +78 -0
  18. package/dist/backlog-sync-validator.js +294 -0
  19. package/dist/branch-drift.d.ts +34 -0
  20. package/dist/branch-drift.js +51 -0
  21. package/dist/cleanup-install-config.d.ts +33 -0
  22. package/dist/cleanup-install-config.js +37 -0
  23. package/dist/cleanup-lock.d.ts +139 -0
  24. package/dist/cleanup-lock.js +313 -0
  25. package/dist/code-path-validator.d.ts +146 -0
  26. package/dist/code-path-validator.js +537 -0
  27. package/dist/code-paths-overlap.d.ts +55 -0
  28. package/dist/code-paths-overlap.js +245 -0
  29. package/dist/commands-logger.d.ts +77 -0
  30. package/dist/commands-logger.js +254 -0
  31. package/dist/commit-message-utils.d.ts +25 -0
  32. package/dist/commit-message-utils.js +41 -0
  33. package/dist/compliance-parser.d.ts +150 -0
  34. package/dist/compliance-parser.js +507 -0
  35. package/dist/constants/backlog-patterns.d.ts +20 -0
  36. package/dist/constants/backlog-patterns.js +23 -0
  37. package/dist/constants/dora-constants.d.ts +49 -0
  38. package/dist/constants/dora-constants.js +53 -0
  39. package/dist/constants/gate-constants.d.ts +15 -0
  40. package/dist/constants/gate-constants.js +15 -0
  41. package/dist/constants/linter-constants.d.ts +16 -0
  42. package/dist/constants/linter-constants.js +16 -0
  43. package/dist/constants/tokenizer-constants.d.ts +15 -0
  44. package/dist/constants/tokenizer-constants.js +15 -0
  45. package/dist/core/scope-checker.d.ts +97 -0
  46. package/dist/core/scope-checker.js +163 -0
  47. package/dist/core/tool-runner.d.ts +161 -0
  48. package/dist/core/tool-runner.js +393 -0
  49. package/dist/core/tool.constants.d.ts +105 -0
  50. package/dist/core/tool.constants.js +101 -0
  51. package/dist/core/tool.schemas.d.ts +226 -0
  52. package/dist/core/tool.schemas.js +226 -0
  53. package/dist/core/worktree-guard.d.ts +130 -0
  54. package/dist/core/worktree-guard.js +242 -0
  55. package/dist/coverage-gate.d.ts +108 -0
  56. package/dist/coverage-gate.js +196 -0
  57. package/dist/date-utils.d.ts +75 -0
  58. package/dist/date-utils.js +140 -0
  59. package/dist/dependency-graph.d.ts +142 -0
  60. package/dist/dependency-graph.js +550 -0
  61. package/dist/dependency-guard.d.ts +54 -0
  62. package/dist/dependency-guard.js +142 -0
  63. package/dist/dependency-validator.d.ts +105 -0
  64. package/dist/dependency-validator.js +154 -0
  65. package/dist/docs-path-validator.d.ts +36 -0
  66. package/dist/docs-path-validator.js +95 -0
  67. package/dist/domain/orchestration.constants.d.ts +99 -0
  68. package/dist/domain/orchestration.constants.js +97 -0
  69. package/dist/domain/orchestration.schemas.d.ts +280 -0
  70. package/dist/domain/orchestration.schemas.js +211 -0
  71. package/dist/domain/orchestration.types.d.ts +133 -0
  72. package/dist/domain/orchestration.types.js +12 -0
  73. package/dist/error-handler.d.ts +116 -0
  74. package/dist/error-handler.js +136 -0
  75. package/dist/file-classifiers.d.ts +62 -0
  76. package/dist/file-classifiers.js +108 -0
  77. package/dist/gates-agent-mode.d.ts +81 -0
  78. package/dist/gates-agent-mode.js +94 -0
  79. package/dist/generate-traceability.d.ts +107 -0
  80. package/dist/generate-traceability.js +411 -0
  81. package/dist/git-adapter.d.ts +395 -0
  82. package/dist/git-adapter.js +649 -0
  83. package/dist/git-staged-validator.d.ts +32 -0
  84. package/dist/git-staged-validator.js +48 -0
  85. package/dist/hardcoded-strings.d.ts +61 -0
  86. package/dist/hardcoded-strings.js +270 -0
  87. package/dist/incremental-lint.d.ts +78 -0
  88. package/dist/incremental-lint.js +129 -0
  89. package/dist/incremental-test.d.ts +39 -0
  90. package/dist/incremental-test.js +61 -0
  91. package/dist/index.d.ts +42 -0
  92. package/dist/index.js +61 -0
  93. package/dist/invariants/check-automated-tests.d.ts +50 -0
  94. package/dist/invariants/check-automated-tests.js +166 -0
  95. package/dist/invariants-runner.d.ts +103 -0
  96. package/dist/invariants-runner.js +527 -0
  97. package/dist/lane-checker.d.ts +50 -0
  98. package/dist/lane-checker.js +319 -0
  99. package/dist/lane-inference.d.ts +39 -0
  100. package/dist/lane-inference.js +195 -0
  101. package/dist/lane-lock.d.ts +211 -0
  102. package/dist/lane-lock.js +474 -0
  103. package/dist/lane-validator.d.ts +48 -0
  104. package/dist/lane-validator.js +114 -0
  105. package/dist/logs-lib.d.ts +104 -0
  106. package/dist/logs-lib.js +207 -0
  107. package/dist/lumenflow-config-schema.d.ts +272 -0
  108. package/dist/lumenflow-config-schema.js +207 -0
  109. package/dist/lumenflow-config.d.ts +95 -0
  110. package/dist/lumenflow-config.js +236 -0
  111. package/dist/manual-test-validator.d.ts +80 -0
  112. package/dist/manual-test-validator.js +200 -0
  113. package/dist/merge-lock.d.ts +115 -0
  114. package/dist/merge-lock.js +251 -0
  115. package/dist/micro-worktree.d.ts +159 -0
  116. package/dist/micro-worktree.js +427 -0
  117. package/dist/migration-deployer.d.ts +69 -0
  118. package/dist/migration-deployer.js +151 -0
  119. package/dist/orchestration-advisory-loader.d.ts +28 -0
  120. package/dist/orchestration-advisory-loader.js +87 -0
  121. package/dist/orchestration-advisory.d.ts +58 -0
  122. package/dist/orchestration-advisory.js +94 -0
  123. package/dist/orchestration-di.d.ts +48 -0
  124. package/dist/orchestration-di.js +57 -0
  125. package/dist/orchestration-rules.d.ts +57 -0
  126. package/dist/orchestration-rules.js +201 -0
  127. package/dist/orphan-detector.d.ts +131 -0
  128. package/dist/orphan-detector.js +226 -0
  129. package/dist/path-classifiers.d.ts +57 -0
  130. package/dist/path-classifiers.js +93 -0
  131. package/dist/piped-command-detector.d.ts +34 -0
  132. package/dist/piped-command-detector.js +64 -0
  133. package/dist/ports/dashboard-renderer.port.d.ts +112 -0
  134. package/dist/ports/dashboard-renderer.port.js +25 -0
  135. package/dist/ports/metrics-collector.port.d.ts +132 -0
  136. package/dist/ports/metrics-collector.port.js +26 -0
  137. package/dist/process-detector.d.ts +84 -0
  138. package/dist/process-detector.js +172 -0
  139. package/dist/prompt-linter.d.ts +72 -0
  140. package/dist/prompt-linter.js +312 -0
  141. package/dist/prompt-monitor.d.ts +15 -0
  142. package/dist/prompt-monitor.js +205 -0
  143. package/dist/rebase-artifact-cleanup.d.ts +145 -0
  144. package/dist/rebase-artifact-cleanup.js +433 -0
  145. package/dist/retry-strategy.d.ts +189 -0
  146. package/dist/retry-strategy.js +283 -0
  147. package/dist/risk-detector.d.ts +108 -0
  148. package/dist/risk-detector.js +252 -0
  149. package/dist/rollback-utils.d.ts +76 -0
  150. package/dist/rollback-utils.js +104 -0
  151. package/dist/section-headings.d.ts +43 -0
  152. package/dist/section-headings.js +49 -0
  153. package/dist/spawn-escalation.d.ts +90 -0
  154. package/dist/spawn-escalation.js +253 -0
  155. package/dist/spawn-monitor.d.ts +229 -0
  156. package/dist/spawn-monitor.js +672 -0
  157. package/dist/spawn-recovery.d.ts +82 -0
  158. package/dist/spawn-recovery.js +298 -0
  159. package/dist/spawn-registry-schema.d.ts +98 -0
  160. package/dist/spawn-registry-schema.js +108 -0
  161. package/dist/spawn-registry-store.d.ts +146 -0
  162. package/dist/spawn-registry-store.js +273 -0
  163. package/dist/spawn-tree.d.ts +121 -0
  164. package/dist/spawn-tree.js +285 -0
  165. package/dist/stamp-status-validator.d.ts +84 -0
  166. package/dist/stamp-status-validator.js +134 -0
  167. package/dist/stamp-utils.d.ts +100 -0
  168. package/dist/stamp-utils.js +229 -0
  169. package/dist/state-machine.d.ts +26 -0
  170. package/dist/state-machine.js +83 -0
  171. package/dist/system-map-validator.d.ts +80 -0
  172. package/dist/system-map-validator.js +272 -0
  173. package/dist/telemetry.d.ts +80 -0
  174. package/dist/telemetry.js +213 -0
  175. package/dist/token-counter.d.ts +51 -0
  176. package/dist/token-counter.js +145 -0
  177. package/dist/usecases/get-dashboard-data.usecase.d.ts +52 -0
  178. package/dist/usecases/get-dashboard-data.usecase.js +61 -0
  179. package/dist/usecases/get-suggestions.usecase.d.ts +100 -0
  180. package/dist/usecases/get-suggestions.usecase.js +153 -0
  181. package/dist/user-normalizer.d.ts +41 -0
  182. package/dist/user-normalizer.js +141 -0
  183. package/dist/validators/phi-constants.d.ts +97 -0
  184. package/dist/validators/phi-constants.js +152 -0
  185. package/dist/validators/phi-scanner.d.ts +58 -0
  186. package/dist/validators/phi-scanner.js +215 -0
  187. package/dist/worktree-ownership.d.ts +50 -0
  188. package/dist/worktree-ownership.js +74 -0
  189. package/dist/worktree-scanner.d.ts +103 -0
  190. package/dist/worktree-scanner.js +168 -0
  191. package/dist/worktree-symlink.d.ts +99 -0
  192. package/dist/worktree-symlink.js +359 -0
  193. package/dist/wu-backlog-updater.d.ts +17 -0
  194. package/dist/wu-backlog-updater.js +37 -0
  195. package/dist/wu-checkpoint.d.ts +124 -0
  196. package/dist/wu-checkpoint.js +233 -0
  197. package/dist/wu-claim-helpers.d.ts +26 -0
  198. package/dist/wu-claim-helpers.js +63 -0
  199. package/dist/wu-claim-resume.d.ts +106 -0
  200. package/dist/wu-claim-resume.js +276 -0
  201. package/dist/wu-consistency-checker.d.ts +95 -0
  202. package/dist/wu-consistency-checker.js +567 -0
  203. package/dist/wu-constants.d.ts +1275 -0
  204. package/dist/wu-constants.js +1382 -0
  205. package/dist/wu-create-validators.d.ts +42 -0
  206. package/dist/wu-create-validators.js +93 -0
  207. package/dist/wu-done-branch-only.d.ts +63 -0
  208. package/dist/wu-done-branch-only.js +191 -0
  209. package/dist/wu-done-messages.d.ts +119 -0
  210. package/dist/wu-done-messages.js +185 -0
  211. package/dist/wu-done-pr.d.ts +72 -0
  212. package/dist/wu-done-pr.js +174 -0
  213. package/dist/wu-done-retry-helpers.d.ts +85 -0
  214. package/dist/wu-done-retry-helpers.js +172 -0
  215. package/dist/wu-done-ui.d.ts +37 -0
  216. package/dist/wu-done-ui.js +69 -0
  217. package/dist/wu-done-validators.d.ts +411 -0
  218. package/dist/wu-done-validators.js +1229 -0
  219. package/dist/wu-done-worktree.d.ts +182 -0
  220. package/dist/wu-done-worktree.js +1097 -0
  221. package/dist/wu-helpers.d.ts +128 -0
  222. package/dist/wu-helpers.js +248 -0
  223. package/dist/wu-lint.d.ts +70 -0
  224. package/dist/wu-lint.js +234 -0
  225. package/dist/wu-paths.d.ts +171 -0
  226. package/dist/wu-paths.js +178 -0
  227. package/dist/wu-preflight-validators.d.ts +86 -0
  228. package/dist/wu-preflight-validators.js +251 -0
  229. package/dist/wu-recovery.d.ts +138 -0
  230. package/dist/wu-recovery.js +341 -0
  231. package/dist/wu-repair-core.d.ts +131 -0
  232. package/dist/wu-repair-core.js +669 -0
  233. package/dist/wu-schema-normalization.d.ts +17 -0
  234. package/dist/wu-schema-normalization.js +82 -0
  235. package/dist/wu-schema.d.ts +793 -0
  236. package/dist/wu-schema.js +881 -0
  237. package/dist/wu-spawn-helpers.d.ts +121 -0
  238. package/dist/wu-spawn-helpers.js +271 -0
  239. package/dist/wu-spawn.d.ts +158 -0
  240. package/dist/wu-spawn.js +1306 -0
  241. package/dist/wu-state-schema.d.ts +213 -0
  242. package/dist/wu-state-schema.js +156 -0
  243. package/dist/wu-state-store.d.ts +264 -0
  244. package/dist/wu-state-store.js +691 -0
  245. package/dist/wu-status-transition.d.ts +63 -0
  246. package/dist/wu-status-transition.js +382 -0
  247. package/dist/wu-status-updater.d.ts +25 -0
  248. package/dist/wu-status-updater.js +116 -0
  249. package/dist/wu-transaction-collectors.d.ts +116 -0
  250. package/dist/wu-transaction-collectors.js +272 -0
  251. package/dist/wu-transaction.d.ts +170 -0
  252. package/dist/wu-transaction.js +273 -0
  253. package/dist/wu-validation-constants.d.ts +60 -0
  254. package/dist/wu-validation-constants.js +66 -0
  255. package/dist/wu-validation.d.ts +118 -0
  256. package/dist/wu-validation.js +243 -0
  257. package/dist/wu-validator.d.ts +62 -0
  258. package/dist/wu-validator.js +325 -0
  259. package/dist/wu-yaml-fixer.d.ts +97 -0
  260. package/dist/wu-yaml-fixer.js +264 -0
  261. package/dist/wu-yaml.d.ts +86 -0
  262. package/dist/wu-yaml.js +222 -0
  263. package/package.json +114 -0
@@ -0,0 +1,649 @@
1
+ /**
2
+ * @file git-adapter.mjs
3
+ * @description Git operations adapter using simple-git library
4
+ * WU-1082: Extract shared utilities (eliminate run() duplication)
5
+ * WU-1213: Refactor to use simple-git library (eliminate execSync)
6
+ * WU-2242: Add runtime type assertions to prevent silent API misuse
7
+ *
8
+ * Replaces run() function in:
9
+ * - tools/wu-claim.mjs
10
+ * - tools/wu-done.mjs
11
+ * - tools/wu-block.mjs
12
+ * - tools/wu-unblock.mjs
13
+ * - tools/wu-create.mjs
14
+ * - tools/wu-cleanup.mjs
15
+ * - tools/gates-pre-commit.mjs
16
+ * - tools/validate.mjs
17
+ * - tools/guard-worktree-commit.mjs
18
+ */
19
+ import { simpleGit } from 'simple-git';
20
+ import { existsSync, rmSync } from 'node:fs';
21
+ import { GIT_FLAGS } from './wu-constants.js';
22
+ // WU-2242: Runtime assertion helpers
23
+ /**
24
+ * Assert that a value is a non-empty string
25
+ * @param {*} value - Value to check
26
+ * @param {string} name - Parameter name for error message
27
+ * @throws {TypeError} If value is not a string
28
+ * @throws {Error} If value is an empty string
29
+ */
30
+ function assertNonEmptyString(value, name) {
31
+ if (typeof value !== 'string') {
32
+ throw new TypeError(`${name} must be a string, got ${typeof value}`);
33
+ }
34
+ if (value === '') {
35
+ throw new Error(`${name} must be a non-empty string`);
36
+ }
37
+ }
38
+ /**
39
+ * Assert that a value is a string (if provided)
40
+ * @param {*} value - Value to check
41
+ * @param {string} name - Parameter name for error message
42
+ * @throws {TypeError} If value is not a string (and not null/undefined)
43
+ */
44
+ function assertOptionalString(value, name) {
45
+ if (value !== undefined && value !== null && typeof value !== 'string') {
46
+ throw new TypeError(`${name} must be a string, got ${typeof value}`);
47
+ }
48
+ }
49
+ /**
50
+ * Assert that a value is a string or array of strings
51
+ * @param {*} value - Value to check
52
+ * @param {string} name - Parameter name for error message
53
+ * @throws {TypeError} If value is not a string or array
54
+ * @throws {Error} If value is empty string or empty array
55
+ */
56
+ function assertStringOrArray(value, name) {
57
+ if (typeof value === 'string') {
58
+ if (value === '') {
59
+ throw new Error(`${name} must be a non-empty string or array`);
60
+ }
61
+ return;
62
+ }
63
+ if (Array.isArray(value)) {
64
+ if (value.length === 0) {
65
+ throw new Error(`${name} must be a non-empty string or array`);
66
+ }
67
+ return;
68
+ }
69
+ throw new TypeError(`${name} must be a string or array, got ${typeof value}`);
70
+ }
71
+ /**
72
+ * Assert that a value is an array
73
+ * @param {*} value - Value to check
74
+ * @param {string} name - Parameter name for error message
75
+ * @throws {TypeError} If value is not an array
76
+ */
77
+ function assertArray(value, name) {
78
+ if (!Array.isArray(value)) {
79
+ throw new TypeError(`${name} must be an array, got ${typeof value}`);
80
+ }
81
+ }
82
+ /**
83
+ * Git operations adapter with dependency injection support
84
+ * @class GitAdapter
85
+ */
86
+ export class GitAdapter {
87
+ git;
88
+ /**
89
+ * Create a GitAdapter instance
90
+ * @param {object} [options] - Configuration options
91
+ * @param {object} [options.git] - simple-git instance (for testing)
92
+ * @param {string} [options.baseDir] - Base directory for git operations
93
+ */
94
+ constructor(options = {}) {
95
+ if (options.baseDir && process.env.DEBUG) {
96
+ console.log(`[git-adapter] DEBUG: GitAdapter constructor with baseDir=${options.baseDir}`);
97
+ }
98
+ this.git = options.git || simpleGit(options.baseDir);
99
+ }
100
+ /**
101
+ * Get current branch name
102
+ * @returns {Promise<string>} Current branch name
103
+ * @example
104
+ * await git.getCurrentBranch(); // "lane/operations/wu-1213"
105
+ */
106
+ async getCurrentBranch() {
107
+ const result = await this.git.revparse([GIT_FLAGS.ABBREV_REF, 'HEAD']);
108
+ return result.trim();
109
+ }
110
+ /**
111
+ * Get git status (porcelain format string for compatibility)
112
+ * @returns {Promise<string>} Status output in porcelain format
113
+ * @example
114
+ * await git.getStatus(); // " M file.txt\n?? untracked.txt"
115
+ */
116
+ async getStatus() {
117
+ const result = await this.git.raw(['status', GIT_FLAGS.PORCELAIN]);
118
+ return result.trim();
119
+ }
120
+ /**
121
+ * Check if a branch exists
122
+ * @param {string} branch - Branch name
123
+ * @returns {Promise<boolean>} True if branch exists
124
+ * @throws {TypeError} If branch is not a string
125
+ * @throws {Error} If branch is empty
126
+ * @example
127
+ * await git.branchExists('main'); // true
128
+ * await git.branchExists('nonexistent'); // false
129
+ */
130
+ async branchExists(branch) {
131
+ assertNonEmptyString(branch, 'branch');
132
+ try {
133
+ await this.git.raw(['rev-parse', '--verify', branch]);
134
+ return true;
135
+ }
136
+ catch {
137
+ return false;
138
+ }
139
+ }
140
+ /**
141
+ * Fetch from remote
142
+ * @param {string} [remote] - Remote name (defaults to fetching all)
143
+ * @param {string} [branch] - Branch name
144
+ * @throws {TypeError} If remote or branch is not a string (when provided)
145
+ * @example
146
+ * await git.fetch('origin', 'main');
147
+ * await git.fetch(); // Fetch all remotes
148
+ */
149
+ async fetch(remote, branch) {
150
+ assertOptionalString(remote, 'remote');
151
+ assertOptionalString(branch, 'branch');
152
+ if (remote && branch) {
153
+ await this.git.fetch(remote, branch);
154
+ }
155
+ else if (remote) {
156
+ await this.git.fetch(remote);
157
+ }
158
+ else {
159
+ await this.git.fetch();
160
+ }
161
+ }
162
+ /**
163
+ * Pull from remote branch
164
+ * @param {string} remote - Remote name
165
+ * @param {string} branch - Branch name
166
+ * @throws {TypeError} If remote or branch is not a string
167
+ * @example
168
+ * await git.pull('origin', 'main');
169
+ */
170
+ async pull(remote, branch) {
171
+ assertNonEmptyString(remote, 'remote');
172
+ assertNonEmptyString(branch, 'branch');
173
+ await this.git.pull(remote, branch);
174
+ }
175
+ /**
176
+ * Get git config value
177
+ * @param {string} key - Config key (e.g., 'user.email')
178
+ * @returns {Promise<string>} Config value
179
+ * @example
180
+ * await git.getConfigValue('user.email'); // "user@example.com"
181
+ */
182
+ async getConfigValue(key) {
183
+ const result = await this.git.raw(['config', key]);
184
+ return result.trim();
185
+ }
186
+ /**
187
+ * Check if working tree is clean (no uncommitted changes)
188
+ * @returns {Promise<boolean>} True if clean
189
+ * @example
190
+ * await git.isClean(); // true or false
191
+ */
192
+ async isClean() {
193
+ const status = await this.getStatus();
194
+ return status === '';
195
+ }
196
+ /**
197
+ * Add files to staging area
198
+ * @param {string|string[]} files - Files to add
199
+ * @throws {TypeError} If files is not a string or array
200
+ * @throws {Error} If files is empty string or empty array
201
+ * @example
202
+ * await git.add('file.txt');
203
+ * await git.add(['file1.txt', 'file2.txt']);
204
+ * await git.add('.'); // Add all
205
+ */
206
+ async add(files) {
207
+ assertStringOrArray(files, 'files');
208
+ await this.git.add(files);
209
+ }
210
+ /**
211
+ * Add files to staging area including deletions
212
+ *
213
+ * WU-1813: Stages modifications, additions, AND deletions for specified files.
214
+ * Unlike add(), this uses `git add -A` which properly handles tracked file deletions.
215
+ *
216
+ * When files array is empty, stages all changes in the working directory (git add -A .)
217
+ * to catch any deletions that might not have been explicitly listed.
218
+ *
219
+ * @param {string[]} files - Files to add (empty array = add all)
220
+ * @example
221
+ * await git.addWithDeletions(['modified.txt', 'deleted.txt']);
222
+ * await git.addWithDeletions([]); // Add all changes including deletions
223
+ */
224
+ async addWithDeletions(files) {
225
+ if (files && files.length > 0) {
226
+ // Stage specific files with -A flag to include deletions
227
+ // Using -- separator for safety with paths that might look like flags
228
+ await this.git.raw(['add', '-A', '--', ...files]);
229
+ }
230
+ else {
231
+ // Stage all changes including deletions
232
+ await this.git.raw(['add', '-A', '.']);
233
+ }
234
+ }
235
+ /**
236
+ * Commit changes
237
+ * @param {string} message - Commit message
238
+ * @throws {TypeError} If message is not a string
239
+ * @throws {Error} If message is empty
240
+ * @example
241
+ * await git.commit('feat: add new feature');
242
+ */
243
+ async commit(message) {
244
+ assertNonEmptyString(message, 'message');
245
+ await this.git.commit(message);
246
+ }
247
+ /**
248
+ * Push to remote
249
+ * @param {string} [remote='origin'] - Remote name
250
+ * @param {string} [branch] - Branch name (uses current branch if not specified)
251
+ * @param {object} [options] - Push options
252
+ * @param {boolean} [options.setUpstream] - Set upstream tracking (-u flag)
253
+ * @throws {TypeError} If remote or branch is not a string (when provided)
254
+ * @example
255
+ * await git.push('origin', 'main');
256
+ * await git.push('origin', 'feature', { setUpstream: true });
257
+ */
258
+ async push(remote = 'origin', branch, options = {}) {
259
+ assertOptionalString(remote, 'remote');
260
+ assertOptionalString(branch, 'branch');
261
+ const pushOptions = {};
262
+ if (options.setUpstream) {
263
+ pushOptions[GIT_FLAGS.UPSTREAM] = null;
264
+ }
265
+ await this.git.push(remote, branch, pushOptions);
266
+ }
267
+ /**
268
+ * Push using refspec to push local ref to different remote ref
269
+ *
270
+ * WU-1435: Enables push-only pattern to keep local main pristine.
271
+ * Pushes directly to origin/main without updating local main.
272
+ *
273
+ * @param {string} remote - Remote name (e.g., 'origin')
274
+ * @param {string} localRef - Local ref to push (e.g., 'tmp/wu-claim/wu-123')
275
+ * @param {string} remoteRef - Remote ref to update (e.g., 'main')
276
+ * @example
277
+ * await git.pushRefspec('origin', 'tmp/wu-claim/wu-123', 'main');
278
+ * // Equivalent to: git push origin tmp/wu-claim/wu-123:main
279
+ */
280
+ async pushRefspec(remote, localRef, remoteRef) {
281
+ const refspec = `${localRef}:${remoteRef}`;
282
+ await this.git.push(remote, refspec);
283
+ }
284
+ /**
285
+ * Create and checkout a new branch
286
+ * @param {string} branch - Branch name
287
+ * @param {string} [startPoint] - Starting commit (defaults to HEAD)
288
+ * @throws {TypeError} If branch is not a string, or startPoint is not a string (when provided)
289
+ * @throws {Error} If branch is empty
290
+ * @example
291
+ * await git.createBranch('feature/new-feature');
292
+ * await git.createBranch('hotfix/bug', 'main');
293
+ */
294
+ async createBranch(branch, startPoint) {
295
+ assertNonEmptyString(branch, 'branch');
296
+ assertOptionalString(startPoint, 'startPoint');
297
+ const args = [GIT_FLAGS.BRANCH, branch];
298
+ if (startPoint) {
299
+ args.push(startPoint);
300
+ }
301
+ await this.git.checkout(args);
302
+ }
303
+ /**
304
+ * Checkout a branch
305
+ * @param {string} branch - Branch name
306
+ * @throws {TypeError} If branch is not a string
307
+ * @throws {Error} If branch is empty
308
+ * @example
309
+ * await git.checkout('main');
310
+ */
311
+ async checkout(branch) {
312
+ assertNonEmptyString(branch, 'branch');
313
+ await this.git.checkout(branch);
314
+ }
315
+ /**
316
+ * Get commit hash
317
+ * @param {string} [ref='HEAD'] - Git ref
318
+ * @returns {Promise<string>} Commit hash
319
+ * @example
320
+ * await git.getCommitHash(); // "a1b2c3d..."
321
+ * await git.getCommitHash('main'); // "e4f5g6h..."
322
+ */
323
+ async getCommitHash(ref = 'HEAD') {
324
+ const result = await this.git.revparse([ref]);
325
+ return result.trim();
326
+ }
327
+ /**
328
+ * Merge a branch
329
+ *
330
+ * WU-1749: Bug 1 fix - Return success status and handle false positive failures.
331
+ * simple-git's merge() returns a MergeSummary that we now properly handle.
332
+ *
333
+ * @param {string} branch - Branch to merge
334
+ * @param {object} [options] - Merge options
335
+ * @param {boolean} [options.ffOnly] - Fast-forward only merge
336
+ * @returns {Promise<{success: boolean}>} Merge result
337
+ * @throws {TypeError} If branch is not a string
338
+ * @throws {Error} If branch is empty
339
+ * @example
340
+ * await git.merge('feature-branch');
341
+ * await git.merge('feature-branch', { ffOnly: true });
342
+ */
343
+ async merge(branch, options = {}) {
344
+ assertNonEmptyString(branch, 'branch');
345
+ const args = [];
346
+ if (options.ffOnly) {
347
+ args.push(GIT_FLAGS.FF_ONLY);
348
+ }
349
+ args.push(branch);
350
+ await this.git.merge(args);
351
+ // If we get here without throwing, merge succeeded
352
+ return { success: true };
353
+ }
354
+ /**
355
+ * Get commit log
356
+ *
357
+ * WU-1749: Bug 4 fix - Add log() method for counting commits.
358
+ * Used by wu-done-retry-helpers.mjs to count previous completion attempts.
359
+ *
360
+ * @param {object} [options] - Log options
361
+ * @param {number} [options.maxCount] - Maximum number of commits to return
362
+ * @returns {Promise<{all: Array<{hash: string, message: string}>, total: number}>} Log result
363
+ * @example
364
+ * await git.log({ maxCount: 50 });
365
+ */
366
+ async log(options = {}) {
367
+ return await this.git.log(options);
368
+ }
369
+ // New semantic methods for wu- scripts (WU-1213)
370
+ /**
371
+ * Find common ancestor (merge base) of two refs
372
+ * @param {string} ref1 - First ref
373
+ * @param {string} ref2 - Second ref
374
+ * @returns {Promise<string>} Common ancestor commit hash
375
+ * @throws {TypeError} If ref1 or ref2 is not a string
376
+ * @example
377
+ * await git.mergeBase('main', 'feature'); // "abc123def456"
378
+ */
379
+ async mergeBase(ref1, ref2) {
380
+ assertNonEmptyString(ref1, 'ref1');
381
+ assertNonEmptyString(ref2, 'ref2');
382
+ const result = await this.git.raw(['merge-base', ref1, ref2]);
383
+ return result.trim();
384
+ }
385
+ /**
386
+ * Simulate merge and detect conflicts without touching working tree
387
+ * @param {string} base - Base commit hash
388
+ * @param {string} ref1 - First ref to merge
389
+ * @param {string} ref2 - Second ref to merge
390
+ * @returns {Promise<string>} Merge tree output (contains conflict markers if conflicts exist)
391
+ * @example
392
+ * await git.mergeTree('base123', 'main', 'feature');
393
+ */
394
+ async mergeTree(base, ref1, ref2) {
395
+ const result = await this.git.raw(['merge-tree', base, ref1, ref2]);
396
+ return result;
397
+ }
398
+ /**
399
+ * List commits with various options
400
+ * @param {string[]} args - Arguments to pass to git rev-list
401
+ * @returns {Promise<string>} Rev-list output
402
+ * @example
403
+ * await git.revList(['--count', '--left-right', 'main...feature']); // "5\t0"
404
+ */
405
+ async revList(args) {
406
+ const result = await this.git.raw(['rev-list', ...args]);
407
+ return result.trim();
408
+ }
409
+ /**
410
+ * Add a worktree with new branch
411
+ * @param {string} path - Worktree path
412
+ * @param {string} branch - Branch name
413
+ * @param {string} [startPoint] - Starting commit (defaults to HEAD)
414
+ * @throws {TypeError} If path or branch is not a string
415
+ * @throws {Error} If path or branch is empty
416
+ * @example
417
+ * await git.worktreeAdd('worktrees/feature', 'feature-branch', 'main');
418
+ */
419
+ async worktreeAdd(path, branch, startPoint) {
420
+ assertNonEmptyString(path, 'path');
421
+ assertNonEmptyString(branch, 'branch');
422
+ assertOptionalString(startPoint, 'startPoint');
423
+ const args = ['worktree', 'add', path, GIT_FLAGS.BRANCH, branch];
424
+ if (startPoint) {
425
+ args.push(startPoint);
426
+ }
427
+ await this.git.raw(args);
428
+ }
429
+ /**
430
+ * Remove a worktree
431
+ *
432
+ * WU-1476: Layer 1 defense - explicit directory cleanup after git worktree remove.
433
+ * Git worktree remove can leave orphan directories when:
434
+ * - The worktree was forcefully removed
435
+ * - Git worktree metadata was corrupted
436
+ * - Previous wu:done failed mid-workflow
437
+ *
438
+ * @param {string} worktreePath - Worktree path
439
+ * @param {object} [options] - Remove options
440
+ * @param {boolean} [options.force] - Force removal
441
+ * @example
442
+ * await git.worktreeRemove('worktrees/feature');
443
+ * await git.worktreeRemove('worktrees/feature', { force: true });
444
+ */
445
+ async worktreeRemove(worktreePath, options = {}) {
446
+ const args = ['worktree', 'remove'];
447
+ if (options.force) {
448
+ args.push(GIT_FLAGS.FORCE);
449
+ }
450
+ args.push(worktreePath);
451
+ // Attempt git worktree remove
452
+ try {
453
+ await this.git.raw(args);
454
+ }
455
+ catch (err) {
456
+ // If git fails, we still want to clean up the directory
457
+ // Re-throw after cleanup attempt to report the original error
458
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool with validated worktree path
459
+ if (existsSync(worktreePath)) {
460
+ rmSync(worktreePath, { recursive: true, force: true });
461
+ }
462
+ throw err;
463
+ }
464
+ // Layer 1 defense (WU-1476): Explicit cleanup if directory still exists
465
+ // This handles edge cases where git worktree remove succeeds but leaves the directory
466
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool with validated worktree path
467
+ if (existsSync(worktreePath)) {
468
+ rmSync(worktreePath, { recursive: true, force: true });
469
+ }
470
+ }
471
+ /**
472
+ * List all worktrees
473
+ * @returns {Promise<string>} Worktree list in porcelain format
474
+ * @example
475
+ * await git.worktreeList();
476
+ */
477
+ async worktreeList() {
478
+ const result = await this.git.raw(['worktree', 'list', GIT_FLAGS.PORCELAIN]);
479
+ return result;
480
+ }
481
+ /**
482
+ * Delete a branch
483
+ * @param {string} branch - Branch name
484
+ * @param {object} [options] - Delete options
485
+ * @param {boolean} [options.force] - Force delete (use -D instead of -d)
486
+ * @throws {TypeError} If branch is not a string
487
+ * @throws {Error} If branch is empty
488
+ * @example
489
+ * await git.deleteBranch('feature');
490
+ * await git.deleteBranch('feature', { force: true });
491
+ */
492
+ async deleteBranch(branch, options = {}) {
493
+ assertNonEmptyString(branch, 'branch');
494
+ const flag = options.force ? GIT_FLAGS.DELETE_FORCE : GIT_FLAGS.DELETE;
495
+ await this.git.branch([flag, branch]);
496
+ }
497
+ /**
498
+ * Create a branch WITHOUT switching to it (WU-1262)
499
+ * Used for micro-worktree pattern where main checkout stays on main
500
+ * @param {string} branch - Branch name to create
501
+ * @param {string} [startPoint] - Starting commit (defaults to HEAD)
502
+ * @example
503
+ * await git.createBranchNoCheckout('tmp/wu-create/wu-123', 'main');
504
+ */
505
+ async createBranchNoCheckout(branch, startPoint) {
506
+ const args = ['branch', branch];
507
+ if (startPoint) {
508
+ args.push(startPoint);
509
+ }
510
+ await this.git.raw(args);
511
+ }
512
+ /**
513
+ * Add a worktree for an EXISTING branch (WU-1262)
514
+ * Unlike worktreeAdd, this doesn't create a new branch
515
+ * @param {string} path - Worktree path
516
+ * @param {string} branch - Existing branch name
517
+ * @example
518
+ * await git.worktreeAddExisting('/tmp/wu-create-xyz', 'tmp/wu-create/wu-123');
519
+ */
520
+ async worktreeAddExisting(path, branch) {
521
+ await this.git.raw(['worktree', 'add', path, branch]);
522
+ }
523
+ /**
524
+ * Rebase current branch onto target (WU-1262)
525
+ * Used in micro-worktree to rebase temp branch when main moves
526
+ * @param {string} onto - Target ref to rebase onto
527
+ * @throws {TypeError} If onto is not a string
528
+ * @throws {Error} If onto is empty
529
+ * @example
530
+ * await git.rebase('main');
531
+ */
532
+ async rebase(onto) {
533
+ assertNonEmptyString(onto, 'onto');
534
+ const gitWithEditor = this.git.env({ ...process.env, GIT_EDITOR: 'true' });
535
+ await gitWithEditor.rebase([onto]);
536
+ }
537
+ /**
538
+ * Reset HEAD to specified commit
539
+ * @param {string} [ref] - Commit ref to reset to (defaults to HEAD)
540
+ * @param {object} [options] - Reset options
541
+ * @param {boolean} [options.hard] - Hard reset (--hard flag)
542
+ * @example
543
+ * await git.reset('abc123', { hard: true });
544
+ */
545
+ async reset(ref, options = {}) {
546
+ const args = ['reset'];
547
+ if (options.hard) {
548
+ args.push(GIT_FLAGS.HARD);
549
+ }
550
+ if (ref) {
551
+ args.push(ref);
552
+ }
553
+ await this.git.raw(args);
554
+ }
555
+ /**
556
+ * Execute arbitrary git command via raw()
557
+ * @param {string[]} args - Git command arguments
558
+ * @returns {Promise<string>} Command output
559
+ * @throws {TypeError} If args is not an array
560
+ * @example
561
+ * await git.raw(['status', '--porcelain']);
562
+ */
563
+ async raw(args) {
564
+ assertArray(args, 'args');
565
+ const result = await this.git.raw(args);
566
+ return result;
567
+ }
568
+ // Deprecated methods (for backward compatibility during migration)
569
+ /**
570
+ * @deprecated Use async methods directly instead
571
+ * Legacy method for backward compatibility
572
+ * Execute a git command and return trimmed output
573
+ * @param {string} cmd - Command to execute
574
+ * @returns {string} Trimmed command output
575
+ */
576
+ run(cmd) {
577
+ throw new Error('GitAdapter.run() is deprecated (WU-1213). Use async methods instead. ' +
578
+ `Attempted to run: ${cmd}`);
579
+ }
580
+ /**
581
+ * @deprecated Use worktreeAdd() instead
582
+ */
583
+ addWorktree(path, branch, startPoint) {
584
+ return this.worktreeAdd(path, branch, startPoint);
585
+ }
586
+ /**
587
+ * @deprecated Use worktreeRemove() instead
588
+ */
589
+ removeWorktree(path, options) {
590
+ return this.worktreeRemove(path, options);
591
+ }
592
+ }
593
+ // WU-1235: Factory functions for explicit directory control
594
+ /**
595
+ * Create a GitAdapter for a specific directory
596
+ * Use this when you need git operations in an explicit path (e.g., worktree vs main)
597
+ * @param {string} baseDir - Directory for git operations
598
+ * @returns {GitAdapter} New GitAdapter instance for the specified directory
599
+ * @example
600
+ * const gitWorktree = createGitForPath('/path/to/worktree');
601
+ * const gitMain = createGitForPath('/path/to/main');
602
+ */
603
+ export function createGitForPath(baseDir) {
604
+ return new GitAdapter({ baseDir });
605
+ }
606
+ /**
607
+ * Create a GitAdapter for the current working directory
608
+ * Captures process.cwd() at call time (not import time)
609
+ * Use this after process.chdir() to get an adapter for the new directory
610
+ * @returns {GitAdapter} New GitAdapter instance for current process.cwd()
611
+ * @example
612
+ * process.chdir(worktreePath);
613
+ * const git = getGitForCwd(); // Uses new directory
614
+ */
615
+ export function getGitForCwd() {
616
+ const cwd = process.cwd();
617
+ if (process.env.DEBUG) {
618
+ console.log(`[git-adapter] DEBUG: getGitForCwd() creating adapter with baseDir=${cwd}`);
619
+ }
620
+ return new GitAdapter({ baseDir: cwd });
621
+ }
622
+ // Singleton deprecation tracking
623
+ let singletonWarned = false;
624
+ /**
625
+ * Reset singleton warning flag (for testing only)
626
+ * @internal
627
+ */
628
+ export function _resetSingletonWarning() {
629
+ singletonWarned = false;
630
+ }
631
+ /**
632
+ * @deprecated Use createGitForPath() or getGitForCwd() instead
633
+ * Singleton GitAdapter instance - captured cwd at module load time
634
+ * WARNING: Does not respect process.chdir() - use factory functions for worktree operations
635
+ * @type {GitAdapter}
636
+ */
637
+ const gitSingleton = new GitAdapter();
638
+ export const git = new Proxy(gitSingleton, {
639
+ get(target, prop) {
640
+ if (!singletonWarned) {
641
+ console.warn('[DEPRECATED] git singleton captured cwd at import time. ' +
642
+ 'Use createGitForPath(path) or getGitForCwd() for explicit directory control.');
643
+ singletonWarned = true;
644
+ }
645
+ const value = target[prop];
646
+ // Bind methods to preserve 'this' context
647
+ return typeof value === 'function' ? value.bind(target) : value;
648
+ },
649
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Git Staged Files Validator
3
+ *
4
+ * Centralized validation for staged files requirement.
5
+ * Extracted from duplicate implementations in wu-block and wu-unblock (WU-1341).
6
+ *
7
+ * Used in --no-auto mode to enforce that required files are staged before commit.
8
+ *
9
+ * @module git-staged-validator
10
+ */
11
+ /**
12
+ * Ensure all required paths are staged in git index
13
+ *
14
+ * Validates that specified files/directories are staged for commit.
15
+ * Supports exact path matching and directory prefix matching.
16
+ *
17
+ * @param {Array<string|null|undefined>} paths - Paths to check (null/undefined values filtered out)
18
+ * @throws {Error} If any required paths are not staged
19
+ * @returns {Array<string>} List of all staged files
20
+ *
21
+ * @example
22
+ * // All files staged - success
23
+ * ensureStaged(['docs/file.md', 'tools/script.js']);
24
+ *
25
+ * // Directory prefix - matches all files under directory
26
+ * ensureStaged(['docs/04-operations/']);
27
+ *
28
+ * // Missing files - throws error
29
+ * ensureStaged(['docs/file1.md', 'docs/file2.md']);
30
+ * // Error: Stage updates for: docs/file2.md
31
+ */
32
+ export declare function ensureStaged(paths: Array<string | null | undefined>): string[];