wogiflow 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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Git Worktree Isolation Module
5
+ *
6
+ * Provides safe task execution by running work in isolated git worktrees.
7
+ * Inspired by Auto-Claude's approach, but available for ALL modes.
8
+ *
9
+ * Benefits:
10
+ * - Parallel execution without conflicts
11
+ * - Safe rollback on failure
12
+ * - Clean branch history
13
+ * - No pollution of main working directory
14
+ *
15
+ * Usage:
16
+ * const { createWorktree, commitAndMerge, discardWorktree } = require('./flow-worktree');
17
+ *
18
+ * const worktree = await createWorktree({ taskId: 'TASK-123', baseBranch: 'main' });
19
+ * // ... do work in worktree.path ...
20
+ * await commitAndMerge(worktree, 'feat: implement feature');
21
+ * // OR on failure:
22
+ * await discardWorktree(worktree);
23
+ */
24
+
25
+ const { execFileSync, spawn } = require('child_process');
26
+ const fs = require('fs');
27
+ const path = require('path');
28
+ const os = require('os');
29
+ const { sanitizeCommitMessage } = require('./flow-security');
30
+
31
+ // ============================================================
32
+ // Configuration
33
+ // ============================================================
34
+
35
+ const WORKTREE_PREFIX = 'wogi-task-';
36
+ const WORKTREE_BASE_DIR = path.join(os.tmpdir(), 'wogi-worktrees');
37
+
38
+ // ============================================================
39
+ // Helper Functions
40
+ // ============================================================
41
+
42
+ /**
43
+ * Execute a git command safely and return the output.
44
+ * Uses execFileSync with array arguments to prevent shell injection.
45
+ *
46
+ * @param {string[]} args - Git arguments as an array (e.g., ['status', '--porcelain'])
47
+ * @param {Object} options - Execution options
48
+ * @returns {string|null} Command output or null on silent failure
49
+ */
50
+ function git(args, options = {}) {
51
+ const { cwd = process.cwd(), silent = false } = options;
52
+ try {
53
+ const result = execFileSync('git', args, {
54
+ cwd,
55
+ encoding: 'utf-8',
56
+ stdio: silent ? 'pipe' : ['pipe', 'pipe', 'pipe']
57
+ });
58
+ return result.trim();
59
+ } catch (error) {
60
+ if (!silent) {
61
+ throw new Error(`Git command failed: git ${args.join(' ')}\n${error.stderr || error.message}`);
62
+ }
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Check if git is available and we're in a repo
69
+ */
70
+ function isGitRepo(cwd = process.cwd()) {
71
+ try {
72
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd, stdio: 'pipe' });
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get the current branch name
81
+ */
82
+ function getCurrentBranch(cwd = process.cwd()) {
83
+ return git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, silent: true }) || 'main';
84
+ }
85
+
86
+ /**
87
+ * Get the root of the git repository
88
+ */
89
+ function getRepoRoot(cwd = process.cwd()) {
90
+ return git(['rev-parse', '--show-toplevel'], { cwd, silent: true });
91
+ }
92
+
93
+ /**
94
+ * Generate a unique worktree branch name
95
+ */
96
+ function generateBranchName(taskId, timestamp = Date.now()) {
97
+ const sanitizedTaskId = taskId
98
+ .replace(/[^a-zA-Z0-9-]/g, '-')
99
+ .toLowerCase();
100
+ return `${WORKTREE_PREFIX}${sanitizedTaskId}-${timestamp}`;
101
+ }
102
+
103
+ /**
104
+ * Generate the worktree path
105
+ */
106
+ function generateWorktreePath(branchName) {
107
+ // Ensure base directory exists
108
+ if (!fs.existsSync(WORKTREE_BASE_DIR)) {
109
+ fs.mkdirSync(WORKTREE_BASE_DIR, { recursive: true });
110
+ }
111
+ return path.join(WORKTREE_BASE_DIR, branchName);
112
+ }
113
+
114
+ // ============================================================
115
+ // Core Functions
116
+ // ============================================================
117
+
118
+ /**
119
+ * Create an isolated worktree for a task
120
+ *
121
+ * @param {Object} options
122
+ * @param {string} options.taskId - Task identifier (e.g., 'TASK-123')
123
+ * @param {string} [options.baseBranch] - Branch to base work on (default: current branch)
124
+ * @param {string} [options.repoRoot] - Repository root (default: auto-detect)
125
+ * @returns {Object} Worktree info { path, branchName, baseBranch, repoRoot }
126
+ */
127
+ async function createWorktree(options = {}) {
128
+ const {
129
+ taskId = 'unnamed-task',
130
+ baseBranch,
131
+ repoRoot: providedRoot
132
+ } = options;
133
+
134
+ // Validate git repo
135
+ const repoRoot = providedRoot || getRepoRoot();
136
+ if (!repoRoot) {
137
+ throw new Error('Not in a git repository');
138
+ }
139
+
140
+ // Determine base branch
141
+ const base = baseBranch || getCurrentBranch(repoRoot);
142
+
143
+ // Generate unique branch and path
144
+ const branchName = generateBranchName(taskId);
145
+ const worktreePath = generateWorktreePath(branchName);
146
+
147
+ // Clean up if path already exists (shouldn't happen, but be safe)
148
+ if (fs.existsSync(worktreePath)) {
149
+ fs.rmSync(worktreePath, { recursive: true, force: true });
150
+ }
151
+
152
+ // Create the worktree with a new branch
153
+ try {
154
+ git(['worktree', 'add', '-b', branchName, worktreePath, base], { cwd: repoRoot });
155
+ } catch (error) {
156
+ throw new Error(`Failed to create worktree: ${error.message}`);
157
+ }
158
+
159
+ const worktreeInfo = {
160
+ path: worktreePath,
161
+ branchName,
162
+ baseBranch: base,
163
+ repoRoot,
164
+ taskId,
165
+ createdAt: new Date().toISOString()
166
+ };
167
+
168
+ // Save worktree info for recovery
169
+ const infoPath = path.join(worktreePath, '.wogi-worktree.json');
170
+ fs.writeFileSync(infoPath, JSON.stringify(worktreeInfo, null, 2));
171
+
172
+ return worktreeInfo;
173
+ }
174
+
175
+ /**
176
+ * Commit changes in the worktree and merge back to base branch
177
+ *
178
+ * @param {Object} worktree - Worktree info from createWorktree
179
+ * @param {string} commitMessage - Commit message
180
+ * @param {Object} [options]
181
+ * @param {boolean} [options.push] - Push after merge (default: false)
182
+ * @param {boolean} [options.squash] - Squash commits on merge (default: true)
183
+ * @param {boolean} [options.cleanup] - Remove worktree after merge (default: true)
184
+ */
185
+ async function commitAndMerge(worktree, commitMessage, options = {}) {
186
+ const {
187
+ push = false,
188
+ squash = true,
189
+ cleanup = true
190
+ } = options;
191
+
192
+ const { path: worktreePath, branchName, baseBranch, repoRoot } = worktree;
193
+
194
+ // Check for changes
195
+ const status = git(['status', '--porcelain'], { cwd: worktreePath, silent: true });
196
+ if (!status) {
197
+ // No changes to commit, just cleanup
198
+ if (cleanup) {
199
+ await discardWorktree(worktree);
200
+ }
201
+ return { merged: false, reason: 'no-changes' };
202
+ }
203
+
204
+ // Sanitize commit message to prevent injection
205
+ const safeMessage = sanitizeCommitMessage(commitMessage);
206
+
207
+ // Stage and commit in worktree
208
+ git(['add', '-A'], { cwd: worktreePath });
209
+ git(['commit', '-m', safeMessage], { cwd: worktreePath });
210
+
211
+ // Switch to base branch in main repo
212
+ const originalBranch = getCurrentBranch(repoRoot);
213
+
214
+ try {
215
+ // Checkout base branch
216
+ git(['checkout', baseBranch], { cwd: repoRoot });
217
+
218
+ // Merge the worktree branch
219
+ if (squash) {
220
+ git(['merge', '--squash', branchName], { cwd: repoRoot });
221
+ git(['commit', '-m', safeMessage], { cwd: repoRoot });
222
+ } else {
223
+ git(['merge', branchName, '-m', safeMessage], { cwd: repoRoot });
224
+ }
225
+
226
+ // Push if requested
227
+ if (push) {
228
+ git(['push', 'origin', baseBranch], { cwd: repoRoot });
229
+ }
230
+
231
+ } catch (error) {
232
+ // Restore original branch on failure
233
+ git(['checkout', originalBranch], { cwd: repoRoot, silent: true });
234
+ throw new Error(`Merge failed: ${error.message}`);
235
+ }
236
+
237
+ // Cleanup
238
+ if (cleanup) {
239
+ await discardWorktree(worktree, { deleteBranch: true });
240
+ }
241
+
242
+ return { merged: true, commitMessage };
243
+ }
244
+
245
+ /**
246
+ * Discard a worktree without merging (rollback)
247
+ *
248
+ * @param {Object} worktree - Worktree info from createWorktree
249
+ * @param {Object} [options]
250
+ * @param {boolean} [options.deleteBranch] - Also delete the branch (default: true)
251
+ */
252
+ async function discardWorktree(worktree, options = {}) {
253
+ const { deleteBranch = true } = options;
254
+ const { path: worktreePath, branchName, repoRoot } = worktree;
255
+
256
+ // Remove the worktree
257
+ try {
258
+ git(['worktree', 'remove', worktreePath, '--force'], { cwd: repoRoot, silent: true });
259
+ } catch {
260
+ // If git remove fails, try manual cleanup
261
+ if (fs.existsSync(worktreePath)) {
262
+ fs.rmSync(worktreePath, { recursive: true, force: true });
263
+ }
264
+ // Prune worktree list
265
+ git(['worktree', 'prune'], { cwd: repoRoot, silent: true });
266
+ }
267
+
268
+ // Delete the branch
269
+ if (deleteBranch) {
270
+ git(['branch', '-D', branchName], { cwd: repoRoot, silent: true });
271
+ }
272
+
273
+ return { discarded: true };
274
+ }
275
+
276
+ /**
277
+ * List all active wogi worktrees
278
+ *
279
+ * @param {string} [repoRoot] - Repository root
280
+ * @returns {Array} List of worktree info objects
281
+ */
282
+ function listWorktrees(repoRoot = process.cwd()) {
283
+ const root = getRepoRoot(repoRoot) || repoRoot;
284
+ const output = git(['worktree', 'list', '--porcelain'], { cwd: root, silent: true });
285
+ if (!output) return [];
286
+
287
+ const worktrees = [];
288
+ const entries = output.split('\n\n').filter(Boolean);
289
+
290
+ for (const entry of entries) {
291
+ const lines = entry.split('\n');
292
+ const worktreePath = lines.find(l => l.startsWith('worktree '))?.replace('worktree ', '');
293
+ const branch = lines.find(l => l.startsWith('branch '))?.replace('branch refs/heads/', '');
294
+
295
+ if (worktreePath && branch && branch.startsWith(WORKTREE_PREFIX)) {
296
+ // Try to load saved info
297
+ const infoPath = path.join(worktreePath, '.wogi-worktree.json');
298
+ let info = { path: worktreePath, branchName: branch };
299
+
300
+ if (fs.existsSync(infoPath)) {
301
+ try {
302
+ info = JSON.parse(fs.readFileSync(infoPath, 'utf-8'));
303
+ } catch { /* ignore */ }
304
+ }
305
+
306
+ worktrees.push(info);
307
+ }
308
+ }
309
+
310
+ return worktrees;
311
+ }
312
+
313
+ /**
314
+ * Cleanup all stale wogi worktrees
315
+ *
316
+ * @param {string} [repoRoot] - Repository root
317
+ * @param {number} [maxAgeMs] - Max age in milliseconds (default: 24 hours)
318
+ */
319
+ async function cleanupStaleWorktrees(repoRoot = process.cwd(), maxAgeMs = 24 * 60 * 60 * 1000) {
320
+ const worktrees = listWorktrees(repoRoot);
321
+ const now = Date.now();
322
+ const cleaned = [];
323
+
324
+ for (const worktree of worktrees) {
325
+ const createdAt = worktree.createdAt ? new Date(worktree.createdAt).getTime() : 0;
326
+ const age = now - createdAt;
327
+
328
+ if (age > maxAgeMs || !worktree.createdAt) {
329
+ try {
330
+ await discardWorktree(worktree);
331
+ cleaned.push(worktree.branchName);
332
+ } catch { /* ignore cleanup errors */ }
333
+ }
334
+ }
335
+
336
+ return cleaned;
337
+ }
338
+
339
+ /**
340
+ * Run a function in an isolated worktree context
341
+ *
342
+ * @param {Object} options - Options for createWorktree
343
+ * @param {Function} fn - Async function to run, receives (worktreePath, worktreeInfo)
344
+ * @param {Object} [fnOptions]
345
+ * @param {string} [fnOptions.commitMessage] - If provided, commit and merge on success
346
+ * @param {boolean} [fnOptions.keepOnFailure] - Keep worktree on failure for debugging
347
+ */
348
+ async function runInWorktree(options, fn, fnOptions = {}) {
349
+ const { commitMessage, keepOnFailure = false } = fnOptions;
350
+ const worktree = await createWorktree(options);
351
+
352
+ try {
353
+ const result = await fn(worktree.path, worktree);
354
+
355
+ if (commitMessage) {
356
+ await commitAndMerge(worktree, commitMessage);
357
+ } else {
358
+ await discardWorktree(worktree);
359
+ }
360
+
361
+ return { success: true, result, worktree };
362
+ } catch (error) {
363
+ if (!keepOnFailure) {
364
+ await discardWorktree(worktree);
365
+ }
366
+ return {
367
+ success: false,
368
+ error: error.message,
369
+ worktree: keepOnFailure ? worktree : null
370
+ };
371
+ }
372
+ }
373
+
374
+ // ============================================================
375
+ // Exports
376
+ // ============================================================
377
+
378
+ module.exports = {
379
+ // Core functions
380
+ createWorktree,
381
+ commitAndMerge,
382
+ discardWorktree,
383
+
384
+ // Utilities
385
+ listWorktrees,
386
+ cleanupStaleWorktrees,
387
+ runInWorktree,
388
+
389
+ // Helpers
390
+ isGitRepo,
391
+ getCurrentBranch,
392
+ getRepoRoot,
393
+
394
+ // Constants
395
+ WORKTREE_PREFIX,
396
+ WORKTREE_BASE_DIR
397
+ };
398
+
399
+ // ============================================================
400
+ // CLI
401
+ // ============================================================
402
+
403
+ if (require.main === module) {
404
+ const args = process.argv.slice(2);
405
+ const command = args[0];
406
+
407
+ async function main() {
408
+ switch (command) {
409
+ case 'list': {
410
+ const worktrees = listWorktrees();
411
+ if (worktrees.length === 0) {
412
+ console.log('No active wogi worktrees');
413
+ } else {
414
+ console.log('\n📁 Active Wogi Worktrees:\n');
415
+ for (const wt of worktrees) {
416
+ console.log(` ${wt.branchName}`);
417
+ console.log(` Path: ${wt.path}`);
418
+ console.log(` Task: ${wt.taskId || 'unknown'}`);
419
+ console.log(` Created: ${wt.createdAt || 'unknown'}`);
420
+ console.log('');
421
+ }
422
+ }
423
+ break;
424
+ }
425
+
426
+ case 'cleanup': {
427
+ const cleaned = await cleanupStaleWorktrees();
428
+ if (cleaned.length === 0) {
429
+ console.log('No stale worktrees to clean up');
430
+ } else {
431
+ console.log(`Cleaned up ${cleaned.length} stale worktree(s):`);
432
+ cleaned.forEach(b => console.log(` - ${b}`));
433
+ }
434
+ break;
435
+ }
436
+
437
+ case 'create': {
438
+ const taskId = args[1] || 'test-task';
439
+ const worktree = await createWorktree({ taskId });
440
+ console.log('\n✅ Created worktree:');
441
+ console.log(` Branch: ${worktree.branchName}`);
442
+ console.log(` Path: ${worktree.path}`);
443
+ console.log(` Base: ${worktree.baseBranch}`);
444
+ break;
445
+ }
446
+
447
+ case 'discard': {
448
+ const branchName = args[1];
449
+ if (!branchName) {
450
+ console.error('Usage: flow-worktree.js discard <branch-name>');
451
+ process.exit(1);
452
+ }
453
+ const worktrees = listWorktrees();
454
+ const worktree = worktrees.find(w => w.branchName === branchName);
455
+ if (!worktree) {
456
+ console.error(`Worktree not found: ${branchName}`);
457
+ process.exit(1);
458
+ }
459
+ await discardWorktree(worktree);
460
+ console.log(`✅ Discarded worktree: ${branchName}`);
461
+ break;
462
+ }
463
+
464
+ default:
465
+ console.log(`
466
+ Wogi Flow - Git Worktree Isolation
467
+
468
+ Usage:
469
+ node flow-worktree.js <command> [options]
470
+
471
+ Commands:
472
+ list List all active wogi worktrees
473
+ cleanup Remove stale worktrees (>24h old)
474
+ create [taskId] Create a new worktree for testing
475
+ discard <branch> Discard a specific worktree
476
+
477
+ Examples:
478
+ node flow-worktree.js list
479
+ node flow-worktree.js create TASK-123
480
+ node flow-worktree.js cleanup
481
+ `);
482
+ }
483
+ }
484
+
485
+ main().catch(err => {
486
+ console.error('Error:', err.message);
487
+ process.exit(1);
488
+ });
489
+ }
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Base Adapter
5
+ *
6
+ * Base class for CLI-specific hook adapters.
7
+ * Each CLI (Claude Code, Gemini, Codex, etc.) extends this class.
8
+ */
9
+
10
+ /**
11
+ * Base adapter class
12
+ * Provides common functionality and defines the interface for CLI adapters.
13
+ */
14
+ class BaseAdapter {
15
+ constructor(name) {
16
+ this.name = name;
17
+ }
18
+
19
+ /**
20
+ * Get the CLI's hook configuration path
21
+ * @returns {string} Path to hook config file
22
+ */
23
+ getConfigPath() {
24
+ throw new Error('getConfigPath() must be implemented by subclass');
25
+ }
26
+
27
+ /**
28
+ * Get supported hook events for this CLI
29
+ * @returns {string[]} Array of event names
30
+ */
31
+ getSupportedEvents() {
32
+ throw new Error('getSupportedEvents() must be implemented by subclass');
33
+ }
34
+
35
+ /**
36
+ * Transform core result to CLI-specific format
37
+ * @param {string} event - Event name (e.g., 'PreToolUse')
38
+ * @param {Object} coreResult - Result from core module
39
+ * @returns {Object} CLI-specific format
40
+ */
41
+ transformResult(event, coreResult) {
42
+ throw new Error('transformResult() must be implemented by subclass');
43
+ }
44
+
45
+ /**
46
+ * Parse incoming hook input from CLI
47
+ * @param {Object} input - Raw input from CLI
48
+ * @returns {Object} Normalized input
49
+ */
50
+ parseInput(input) {
51
+ return input; // Default: pass through
52
+ }
53
+
54
+ /**
55
+ * Generate hook configuration for this CLI
56
+ * @param {Object} rules - Hook rules from config
57
+ * @returns {Object} CLI-specific hook configuration
58
+ */
59
+ generateConfig(rules) {
60
+ throw new Error('generateConfig() must be implemented by subclass');
61
+ }
62
+
63
+ /**
64
+ * Get the install command for this CLI's hooks
65
+ * @returns {string} Install instructions
66
+ */
67
+ getInstallInstructions() {
68
+ return `Install hooks for ${this.name}`;
69
+ }
70
+
71
+ /**
72
+ * Check if this CLI is available/installed
73
+ * @returns {boolean}
74
+ */
75
+ isAvailable() {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Standard result format from core modules
82
+ * All adapters expect this format from core modules.
83
+ */
84
+ const CoreResultSchema = {
85
+ // Common fields
86
+ allowed: Boolean, // Whether action is allowed
87
+ blocked: Boolean, // Whether action is blocked
88
+ message: String, // Human-readable message
89
+ reason: String, // Machine-readable reason code
90
+
91
+ // Optional fields
92
+ warning: Boolean, // Is this a warning (vs block)?
93
+ task: Object, // Active task info
94
+ similar: Array, // Similar components found
95
+ results: Array, // Validation results
96
+ criteriaStatus: Object // Loop criteria status
97
+ };
98
+
99
+ module.exports = {
100
+ BaseAdapter,
101
+ CoreResultSchema
102
+ };