@itz4blitz/agentful 1.8.0 → 1.8.1

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.
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Ensure Worktree Hook
5
+ *
6
+ * PreToolUse hook that enforces git worktree usage for file modifications.
7
+ *
8
+ * Modes:
9
+ * off - Allow all edits (backward compatible)
10
+ * block - Require existing worktree, reject if not in one
11
+ * auto - Create worktree automatically if not in one
12
+ *
13
+ * Environment Variables:
14
+ * AGENTFUL_WORKTREE_MODE - off|block|auto (default: auto)
15
+ * AGENTFUL_WORKTREE_DIR - Where to create worktrees (default: ../)
16
+ * AGENTFUL_WORKTREE_AUTO_CLEANUP - Auto-remove after completion (default: true)
17
+ * AGENTFUL_WORKTREE_RETENTION_DAYS - Days before cleanup (default: 7)
18
+ * AGENTFUL_WORKTREE_MAX_ACTIVE - Max active worktrees (default: 5)
19
+ *
20
+ * To disable this hook:
21
+ * Temporary: export AGENTFUL_WORKTREE_MODE=off
22
+ * Permanent: Remove from .claude/settings.json PreToolUse hooks
23
+ * Customize: Edit bin/hooks/ensure-worktree.js
24
+ */
25
+
26
+ import fs from 'fs';
27
+ import path from 'path';
28
+ import { execSync } from 'child_process';
29
+ import { fileURLToPath } from 'url';
30
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
31
+
32
+ // Get configuration
33
+ const MODE = process.env.AGENTFUL_WORKTREE_MODE || 'auto';
34
+ const WORKTREE_DIR = process.env.AGENTFUL_WORKTREE_DIR || '../';
35
+ const AUTO_CLEANUP = process.env.AGENTFUL_WORKTREE_AUTO_CLEANUP !== 'false';
36
+ const RETENTION_DAYS = parseInt(process.env.AGENTFUL_WORKTREE_RETENTION_DAYS || '7', 10);
37
+ const MAX_ACTIVE = parseInt(process.env.AGENTFUL_WORKTREE_MAX_ACTIVE || '5', 10);
38
+ const AGENT_TYPE = process.env.AGENTFUL_AGENT_TYPE || 'general';
39
+ const TASK_TYPE = process.env.AGENTFUL_TASK_TYPE || 'general';
40
+
41
+ /**
42
+ * Detect if currently in a git worktree
43
+ */
44
+ function isInWorktree() {
45
+ try {
46
+ const cwd = process.cwd();
47
+ const gitFile = path.join(cwd, '.git');
48
+
49
+ // If .git is a file (not a directory), we're in a worktree
50
+ if (existsSync(gitFile) && statSync(gitFile).isFile()) {
51
+ return true;
52
+ }
53
+ } catch (error) {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Determine worktree purpose from agent/task context
60
+ */
61
+ function determinePurpose() {
62
+ // Agent-based purposes
63
+ const agentPurposes = {
64
+ 'fixer': 'fix',
65
+ 'reviewer': 'review',
66
+ 'tester': 'test',
67
+ 'backend': 'backend',
68
+ 'frontend': 'frontend',
69
+ 'architect': 'architect',
70
+ 'orchestrator': 'orchestrator',
71
+ };
72
+
73
+ // Task-based purposes
74
+ const taskPurposes = {
75
+ 'feature': 'feature',
76
+ 'hotfix': 'hotfix',
77
+ 'bugfix': 'bugfix',
78
+ 'experiment': 'experiment',
79
+ };
80
+
81
+ // Determine purpose based on agent type, then task type
82
+ if (agentPurposes[AGENT_TYPE]) {
83
+ return agentPurposes[AGENT_TYPE];
84
+ }
85
+
86
+ if (taskPurposes[TASK_TYPE]) {
87
+ return taskPurposes[TASK_TYPE];
88
+ }
89
+
90
+ // Default to general
91
+ return 'general';
92
+ }
93
+
94
+ /**
95
+ * Show block message with helpful error
96
+ */
97
+ function showBlockMessage() {
98
+ const branch = getCurrentBranch();
99
+
100
+ console.error(`
101
+ :::error
102
+ ═════════════════════════════════════════════════
103
+ 🚫 Blocked: Direct Repository Edits
104
+ ═════════════════════════════════════════════════
105
+
106
+ You are attempting to edit files in the root repository, but AGENTFUL_WORKTREE_MODE
107
+ is set to "block" which requires working in a git worktree.
108
+
109
+ Current branch: ${branch}
110
+ Current directory: ${process.cwd()}
111
+
112
+ To proceed, choose one option:
113
+
114
+ 1. 🌳 Create a worktree (recommended):
115
+ git worktree add ../my-worktree -b ${branch}
116
+ cd ../my-worktree
117
+
118
+ 2. ⚙️ Change mode to auto (creates worktrees automatically):
119
+ export AGENTFUL_WORKTREE_MODE=auto
120
+
121
+ 3. 🚫 Disable worktree protection (not recommended):
122
+ export AGENTFUL_WORKTREE_MODE=off
123
+
124
+ For more information, see: /agentful-worktree or docs/pages/concepts/git-worktrees.mdx
125
+ :::`);
126
+
127
+ process.exit(1);
128
+ }
129
+
130
+ /**
131
+ * Create worktree automatically
132
+ */
133
+ function createWorktree() {
134
+ const repoRoot = findRepoRoot();
135
+ if (!repoRoot) {
136
+ console.error(':::error Not in a git repository:::');
137
+ process.exit(1);
138
+ }
139
+
140
+ const purpose = determinePurpose();
141
+ const branch = getCurrentBranch();
142
+ const timestamp = Date.now();
143
+
144
+ // Generate worktree name
145
+ const worktreeName = `agentful-${purpose}-${sanitizeBranchName(branch)}-${timestamp}`;
146
+
147
+ // Create worktree
148
+ const worktreePath = path.join(repoRoot, WORKTREE_DIR, worktreeName);
149
+
150
+ console.log(`🌳 Creating worktree: ${worktreeName}`);
151
+ console.log(` Branch: ${branch}`);
152
+ console.log(` Path: ${worktreePath}`);
153
+
154
+ try {
155
+ execSync(
156
+ `git worktree add "${worktreePath}" -b "${branch}"`,
157
+ { cwd: repoRoot, stdio: 'inherit' }
158
+ );
159
+ } catch (error) {
160
+ console.error(`:::error Failed to create worktree: ${error.message}:::`);
161
+ process.exit(1);
162
+ }
163
+
164
+ // Track in state.json (would happen in orchestrator, but this is standalone)
165
+ try {
166
+ const stateFile = path.join(repoRoot, '.agentful', 'state.json');
167
+ if (existsSync(stateFile)) {
168
+ const state = JSON.parse(readFileSync(stateFile, 'utf8'));
169
+ state.current_worktree = {
170
+ name: worktreeName,
171
+ path: worktreePath,
172
+ branch: branch,
173
+ purpose: purpose,
174
+ created_at: new Date().toISOString(),
175
+ };
176
+ writeFileSync(stateFile, JSON.stringify(state, null, 2));
177
+ }
178
+ } catch (error) {
179
+ // State file might not exist yet, that's okay
180
+ }
181
+
182
+ // Export for caller to capture
183
+ console.log(`export AGENTFUL_WORKTREE_DIR="${worktreePath}"`);
184
+
185
+ process.exit(0);
186
+ }
187
+
188
+ /**
189
+ * Main execution
190
+ */
191
+ (() => {
192
+ // Get tool and file from environment
193
+ const tool = process.env.TOOL || '';
194
+ const file = process.env.FILE || '';
195
+
196
+ // Only check Write and Edit tools
197
+ if (tool !== 'Write' && tool !== 'Edit') {
198
+ process.exit(0);
199
+ }
200
+
201
+ // Auto-disable in CI environments
202
+ if (isCIEnvironment()) {
203
+ process.exit(0);
204
+ }
205
+
206
+ // Check if already in worktree (explicitly set or detected)
207
+ if (WORKTREE_DIR) {
208
+ // In worktree - allow operation
209
+ process.exit(0);
210
+ }
211
+
212
+ // Not in worktree - handle based on mode
213
+ switch (MODE) {
214
+ case 'off':
215
+ // Allow all edits silently
216
+ process.exit(0);
217
+ break;
218
+
219
+ case 'block':
220
+ // Require existing worktree
221
+ showBlockMessage();
222
+ break;
223
+
224
+ case 'auto':
225
+ // Create worktree automatically
226
+ createWorktree();
227
+ break;
228
+
229
+ default:
230
+ console.error(`:::warning Invalid AGENTFUL_WORKTREE_MODE: ${MODE}:::`);
231
+ console.error(`:::warning Valid options: off, block, auto:::`);
232
+ process.exit(1);
233
+ }
234
+ })();
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Git Worktree Service
5
+ *
6
+ * Core service for managing git worktrees in agentful.
7
+ * Provides worktree creation, tracking, and cleanup functions.
8
+ *
9
+ * Environment Variables:
10
+ * AGENTFUL_WORKTREE_MODE - auto|block|off (default: auto)
11
+ * AGENTFUL_WORKTREE_LOCATION - Where to create worktrees (default: ../)
12
+ * AGENTFUL_WORKTREE_AUTO_CLEANUP - Auto-remove after completion (default: true)
13
+ * AGENTFUL_WORKTREE_RETENTION_DAYS - Days before cleanup (default: 7)
14
+ * AGENTFUL_WORKTREE_MAX_ACTIVE - Max active worktrees (default: 5)
15
+ *
16
+ * Usage:
17
+ * node worktree-service.js create <purpose> <branch>
18
+ * node worktree-service.js list
19
+ * node worktree-service.js cleanup
20
+ * node worktree-service.js prune
21
+ * node worktree-service.js status
22
+ */
23
+
24
+ import fs from 'fs';
25
+ import path from 'path';
26
+ import { execSync } from 'child_process';
27
+ import { fileURLToPath } from 'url';
28
+
29
+ const __filename = fileURLToPath(import.meta.url);
30
+ const __dirname = path.dirname(__filename);
31
+
32
+ // Configuration from environment
33
+ const CONFIG = {
34
+ mode: process.env.AGENTFUL_WORKTREE_MODE || 'auto',
35
+ location: process.env.AGENTFUL_WORKTREE_LOCATION || '../',
36
+ autoCleanup: process.env.AGENTFUL_WORKTREE_AUTO_CLEANUP !== 'false',
37
+ retentionDays: parseInt(process.env.AGENTFUL_WORKTREE_RETENTION_DAYS || '7', 10),
38
+ maxActive: parseInt(process.env.AGENTFUL_WORKTREE_MAX_ACTIVE || '5', 10),
39
+ };
40
+
41
+ // Paths
42
+ const REPO_ROOT = findRepoRoot();
43
+ const WORKTREES_DIR = path.join(REPO_ROOT, '.git', 'worktrees');
44
+ const TRACKING_FILE = path.join(REPO_ROOT, '.agentful', 'worktrees.json');
45
+
46
+ /**
47
+ * Find git repository root
48
+ */
49
+ function findRepoRoot() {
50
+ try {
51
+ return execSync('git rev-parse --show-toplevel', {
52
+ encoding: 'utf8',
53
+ cwd: process.cwd(),
54
+ }).trim();
55
+ } catch (error) {
56
+ console.error('❌ Not in a git repository');
57
+ process.exit(1);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get current git branch
63
+ */
64
+ function getCurrentBranch() {
65
+ try {
66
+ return execSync('git rev-parse --abbrev-ref HEAD', {
67
+ encoding: 'utf8',
68
+ cwd: process.cwd(),
69
+ }).trim();
70
+ } catch (error) {
71
+ return 'unknown';
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get current working directory relative to repo root
77
+ */
78
+ function getCurrentWorktree() {
79
+ const cwd = process.cwd();
80
+ const gitCommonDir = execSync('git rev-parse --git-common-dir', {
81
+ encoding: 'utf8',
82
+ cwd,
83
+ }).trim();
84
+
85
+ // If .git is a file, we're in a worktree
86
+ const gitFile = path.join(cwd, '.git');
87
+ const isWorktree = fs.existsSync(gitFile) && fs.statSync(gitFile).isFile();
88
+
89
+ if (isWorktree) {
90
+ // Read the worktree path from .git file
91
+ const gitDir = fs.readFileSync(gitFile, 'utf8').trim();
92
+ const worktreePath = gitDir.replace('gitdir: ', '').replace('/.git', '');
93
+ return path.basename(worktreePath);
94
+ }
95
+
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Sanitize branch name for use in worktree name
101
+ */
102
+ function sanitizeBranchName(branch) {
103
+ return branch
104
+ .replace(/\//g, '-') // Replace / with -
105
+ .replace(/[^a-zA-Z0-9-]/g, '') // Remove special chars
106
+ .replace(/-+/g, '-') // Collapse multiple dashes
107
+ .replace(/^-|-$/g, ''); // Trim leading/trailing dashes
108
+ }
109
+
110
+ /**
111
+ * Validate worktree name for security
112
+ */
113
+ function validateWorktreeName(name) {
114
+ // Prevent path traversal
115
+ const resolvedPath = path.resolve(REPO_ROOT, CONFIG.location, name);
116
+ if (!resolvedPath.startsWith(REPO_ROOT)) {
117
+ throw new Error('Invalid worktree name: path traversal detected');
118
+ }
119
+
120
+ // Check for valid characters
121
+ if (!/^[a-zA-Z0-9-]+$/.test(name)) {
122
+ throw new Error('Invalid worktree name: contains invalid characters');
123
+ }
124
+
125
+ return true;
126
+ }
127
+
128
+ /**
129
+ * Get tracked worktrees from .agentful/worktrees.json
130
+ */
131
+ function getTrackedWorktrees() {
132
+ try {
133
+ if (fs.existsSync(TRACKING_FILE)) {
134
+ const content = fs.readFileSync(TRACKING_FILE, 'utf8');
135
+ return JSON.parse(content);
136
+ }
137
+ } catch (error) {
138
+ // Invalid tracking file, start fresh
139
+ console.warn(`⚠️ Corrupted tracking file, starting fresh: ${TRACKING_FILE}`);
140
+ }
141
+
142
+ return { active: [] };
143
+ }
144
+
145
+ /**
146
+ * Save tracked worktrees to .agentful/worktrees.json
147
+ */
148
+ function saveTrackedWorktrees(data) {
149
+ const dir = path.dirname(TRACKING_FILE);
150
+ if (!fs.existsSync(dir)) {
151
+ fs.mkdirSync(dir, { recursive: true });
152
+ }
153
+
154
+ fs.writeFileSync(
155
+ TRACKING_FILE,
156
+ JSON.stringify(data, null, 2),
157
+ 'utf8'
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Get list of all git worktrees
163
+ */
164
+ function getGitWorktrees() {
165
+ try {
166
+ const output = execSync('git worktree list', {
167
+ encoding: 'utf8',
168
+ cwd: REPO_ROOT,
169
+ });
170
+
171
+ return output.trim().split('\n').map(line => {
172
+ const [worktreePath, commit, branch] = line.split(/\s+/);
173
+ return {
174
+ path: worktreePath,
175
+ commit,
176
+ branch: branch.replace(/[\[\]]/g, ''), // Remove [ ] brackets
177
+ };
178
+ });
179
+ } catch (error) {
180
+ return [];
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Check if a branch is already checked out
186
+ */
187
+ function isBranchCheckedOut(branch) {
188
+ const worktrees = getGitWorktrees();
189
+ return worktrees.some(w => w.branch === branch);
190
+ }
191
+
192
+ /**
193
+ * Create a unique branch name if original is already checked out
194
+ */
195
+ function getUniqueBranch(originalBranch, timestamp) {
196
+ if (!isBranchCheckedOut(originalBranch)) {
197
+ return originalBranch;
198
+ }
199
+
200
+ // Create unique branch with timestamp
201
+ const uniqueName = `${originalBranch}-agentful-${timestamp}`;
202
+ console.log(`⚠️ Branch "${originalBranch}" already checked out`);
203
+ console.log(` Creating unique branch: ${uniqueName}`);
204
+ return uniqueName;
205
+ }
206
+
207
+ /**
208
+ * Check disk space
209
+ */
210
+ function checkDiskSpace(requiredBytes) {
211
+ try {
212
+ const stats = fs.statSync(REPO_ROOT);
213
+ // Note: This is a basic check. For production, use df or similar
214
+ return true;
215
+ } catch (error) {
216
+ console.warn('⚠️ Could not check disk space');
217
+ return true;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Create a new worktree
223
+ */
224
+ function createWorktree(purpose, branch = null) {
225
+ const timestamp = Date.now();
226
+ const currentBranch = branch || getCurrentBranch();
227
+ const sanitizedBranch = sanitizeBranchName(currentBranch);
228
+
229
+ // Generate worktree name
230
+ const worktreeName = `agentful-${purpose}-${sanitizedBranch}-${timestamp}`;
231
+
232
+ // Validate
233
+ validateWorktreeName(worktreeName);
234
+
235
+ // Get unique branch if needed
236
+ const targetBranch = getUniqueBranch(currentBranch, timestamp);
237
+
238
+ // Check max active worktrees
239
+ const tracked = getTrackedWorktrees();
240
+ if (tracked.active.length >= CONFIG.maxActive) {
241
+ console.warn(`⚠️ Maximum active worktrees reached (${CONFIG.maxActive})`);
242
+ console.warn(' Run cleanup first: node worktree-service.js cleanup');
243
+ }
244
+
245
+ // Create worktree
246
+ const worktreePath = path.join(REPO_ROOT, CONFIG.location, worktreeName);
247
+
248
+ try {
249
+ console.log(`🌳 Creating worktree: ${worktreeName}`);
250
+ console.log(` Branch: ${targetBranch}`);
251
+ console.log(` Path: ${worktreePath}`);
252
+
253
+ execSync(
254
+ `git worktree add "${worktreePath}" -b "${targetBranch}"`,
255
+ { cwd: REPO_ROOT, stdio: 'inherit' }
256
+ );
257
+
258
+ // Initialize submodules if present
259
+ const submodulePath = path.join(worktreePath, '.gitmodules');
260
+ if (fs.existsSync(submodulePath)) {
261
+ console.log(' 📦 Initializing submodules...');
262
+ execSync('git submodule update --init --recursive', {
263
+ cwd: worktreePath,
264
+ stdio: 'inherit',
265
+ });
266
+ }
267
+
268
+ // Track the worktree
269
+ const worktreeData = {
270
+ name: worktreeName,
271
+ path: worktreePath,
272
+ branch: targetBranch,
273
+ purpose,
274
+ created_at: new Date().toISOString(),
275
+ last_activity: new Date().toISOString(),
276
+ };
277
+
278
+ tracked.active.push(worktreeData);
279
+ saveTrackedWorktrees(tracked);
280
+
281
+ console.log(`✅ Worktree created successfully`);
282
+ console.log(` Export AGENTFUL_WORKTREE_DIR="${worktreePath}"`);
283
+
284
+ // Output worktree path for easy capture
285
+ console.log(`WORKTREE_PATH=${worktreePath}`);
286
+
287
+ return worktreeData;
288
+ } catch (error) {
289
+ console.error(`❌ Failed to create worktree: ${error.message}`);
290
+ process.exit(1);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * List all tracked worktrees
296
+ */
297
+ function listWorktrees() {
298
+ const tracked = getTrackedWorktrees();
299
+ const gitWorktrees = getGitWorktrees();
300
+
301
+ console.log('\n📋 Active Worktrees:\n');
302
+
303
+ if (tracked.active.length === 0) {
304
+ console.log(' No active worktrees\n');
305
+ return;
306
+ }
307
+
308
+ tracked.active.forEach(w => {
309
+ const gitWt = gitWorktrees.find(gw => gw.path === w.path);
310
+ const exists = !!gitWt;
311
+ const ageMs = Date.now() - new Date(w.created_at).getTime();
312
+ const ageMins = Math.floor(ageMs / 60000);
313
+
314
+ let status = exists ? '🟢 Active' : '🔴 Orphaned';
315
+ if (ageMins > 60) status = '🟡 Stale';
316
+
317
+ console.log(`┌─────────────────────────────────────────────────────────────┐`);
318
+ console.log(`│ ${w.name}`);
319
+ console.log(`│ ├─ Branch: ${w.branch}`);
320
+ console.log(`│ ├─ Purpose: ${w.purpose}`);
321
+ console.log(`│ ├─ Path: ${w.path}`);
322
+ console.log(`│ ├─ Status: ${status} (${ageMins} min ago)`);
323
+ console.log(`│ └─ Created: ${w.created_at}`);
324
+ console.log(`└─────────────────────────────────────────────────────────────┘`);
325
+ });
326
+
327
+ console.log('');
328
+ }
329
+
330
+ /**
331
+ * Get status of current session
332
+ */
333
+ function getStatus() {
334
+ const currentWorktree = getCurrentWorktree();
335
+ const tracked = getTrackedWorktrees();
336
+
337
+ if (currentWorktree) {
338
+ const wt = tracked.active.find(w => w.name === currentWorktree);
339
+ if (wt) {
340
+ console.log(`\n🌳 Current worktree: ${wt.name}`);
341
+ console.log(` Branch: ${wt.branch}`);
342
+ console.log(` Purpose: ${wt.purpose}`);
343
+ console.log(` Path: ${wt.path}\n`);
344
+ } else {
345
+ console.log(`\n⚠️ In untracked worktree: ${currentWorktree}`);
346
+ console.log(` Worktree exists but not tracked by agentful\n`);
347
+ }
348
+ } else {
349
+ console.log('\n📍 Working in root repository');
350
+ console.log(' No active worktree for this session\n');
351
+ }
352
+
353
+ console.log(`Mode: ${CONFIG.mode}`);
354
+ console.log(`Location: ${CONFIG.location}`);
355
+ console.log(`Auto-cleanup: ${CONFIG.autoCleanup ? 'enabled' : 'disabled'}`);
356
+ console.log(`Max active: ${CONFIG.maxActive}\n`);
357
+ }
358
+
359
+ /**
360
+ * Cleanup stale worktrees
361
+ */
362
+ function cleanupWorktrees() {
363
+ const tracked = getTrackedWorktrees();
364
+ const now = Date.now();
365
+ const retentionMs = CONFIG.retentionDays * 24 * 60 * 60 * 1000;
366
+ let cleanedCount = 0;
367
+
368
+ console.log(`\n🧹 Cleaning up worktrees older than ${CONFIG.retentionDays} days...\n`);
369
+
370
+ const active = tracked.active.filter(w => {
371
+ const age = now - new Date(w.created_at).getTime();
372
+ const isStale = age > retentionMs;
373
+
374
+ if (isStale) {
375
+ console.log(`🗑️ Removing stale: ${w.name}`);
376
+ try {
377
+ execSync(`git worktree remove "${w.path}"`, {
378
+ cwd: REPO_ROOT,
379
+ stdio: 'inherit',
380
+ });
381
+ cleanedCount++;
382
+ } catch (error) {
383
+ console.warn(` ⚠️ Failed to remove: ${error.message}`);
384
+ }
385
+ }
386
+
387
+ return !isStale; // Keep only non-stale
388
+ });
389
+
390
+ // Save updated tracking
391
+ saveTrackedWorktrees({ active });
392
+
393
+ // Also run git prune to clean metadata
394
+ console.log('\n🧹 Pruning git worktree metadata...');
395
+ try {
396
+ execSync('git worktree prune', { cwd: REPO_ROOT, stdio: 'inherit' });
397
+ } catch (error) {
398
+ console.warn(` ⚠️ Prune warning: ${error.message}`);
399
+ }
400
+
401
+ console.log(`\n✅ Cleanup complete. Removed ${cleanedCount} worktree(s).\n`);
402
+ }
403
+
404
+ /**
405
+ * Prune git worktree metadata
406
+ */
407
+ function pruneWorktrees() {
408
+ console.log('\n🧹 Pruning git worktree metadata...\n');
409
+ try {
410
+ execSync('git worktree prune', { cwd: REPO_ROOT, stdio: 'inherit' });
411
+ console.log('✅ Prune complete\n');
412
+ } catch (error) {
413
+ console.error(`❌ Prune failed: ${error.message}`);
414
+ process.exit(1);
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Remove a specific worktree
420
+ */
421
+ function removeWorktree(worktreeName) {
422
+ const tracked = getTrackedWorktrees();
423
+ const worktree = tracked.active.find(w => w.name === worktreeName);
424
+
425
+ if (!worktree) {
426
+ console.error(`❌ Worktree not found: ${worktreeName}`);
427
+ process.exit(1);
428
+ }
429
+
430
+ try {
431
+ console.log(`🗑️ Removing worktree: ${worktreeName}`);
432
+ execSync(`git worktree remove "${worktree.path}"`, {
433
+ cwd: REPO_ROOT,
434
+ stdio: 'inherit',
435
+ });
436
+
437
+ // Remove from tracking
438
+ const active = tracked.active.filter(w => w.name !== worktreeName);
439
+ saveTrackedWorktrees({ active });
440
+
441
+ console.log('✅ Worktree removed\n');
442
+ } catch (error) {
443
+ console.error(`❌ Failed to remove worktree: ${error.message}`);
444
+ process.exit(1);
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Detect if running in CI environment
450
+ */
451
+ function isCIEnvironment() {
452
+ return process.env.CI === 'true' ||
453
+ process.env.GITHUB_ACTIONS === 'true' ||
454
+ process.env.GITLAB_CI === 'true' ||
455
+ process.env.CIRCLECI === 'true';
456
+ }
457
+
458
+ // CLI interface
459
+ const command = process.argv[2];
460
+ const args = process.argv.slice(3);
461
+
462
+ // Auto-disable worktree mode in CI
463
+ if (isCIEnvironment()) {
464
+ console.log('🤖 CI environment detected. Worktree mode auto-disabled.\n');
465
+ process.exit(0);
466
+ }
467
+
468
+ switch (command) {
469
+ case 'create':
470
+ if (args.length < 1) {
471
+ console.error('Usage: worktree-service.js create <purpose> [branch]');
472
+ console.error('Example: worktree-service.js create feature feature/auth');
473
+ process.exit(1);
474
+ }
475
+ createWorktree(args[0], args[1]);
476
+ break;
477
+
478
+ case 'list':
479
+ listWorktrees();
480
+ break;
481
+
482
+ case 'status':
483
+ getStatus();
484
+ break;
485
+
486
+ case 'cleanup':
487
+ cleanupWorktrees();
488
+ break;
489
+
490
+ case 'prune':
491
+ pruneWorktrees();
492
+ break;
493
+
494
+ case 'remove':
495
+ if (args.length < 1) {
496
+ console.error('Usage: worktree-service.js remove <worktree-name>');
497
+ process.exit(1);
498
+ }
499
+ removeWorktree(args[0]);
500
+ break;
501
+
502
+ default:
503
+ console.log('Git Worktree Service for agentful\n');
504
+ console.log('Usage: node worktree-service.js <command> [args]\n');
505
+ console.log('Commands:');
506
+ console.log(' create <purpose> [branch] Create a new worktree');
507
+ console.log(' list List all tracked worktrees');
508
+ console.log(' status Show current worktree status');
509
+ console.log(' cleanup Remove stale worktrees');
510
+ console.log(' prune Prune git worktree metadata');
511
+ console.log(' remove <name> Remove specific worktree\n');
512
+ console.log(`Environment: AGENTFUL_WORKTREE_MODE=${CONFIG.mode}`);
513
+ process.exit(0);
514
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itz4blitz/agentful",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "Pre-configured AI toolkit with self-hosted execution - works with any LLM, any tech stack, any platform.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -79,6 +79,7 @@
79
79
  "@vitest/coverage-v8": "^1.2.0",
80
80
  "eslint": "^9.39.2",
81
81
  "semantic-release": "^25.0.2",
82
+ "vite-plugin-pagefind": "^1.0.7",
82
83
  "vitest": "^1.2.0",
83
84
  "vocs": "^1.4.1"
84
85
  },
@@ -48,6 +48,25 @@ You are the **Backend Agent**. You implement server-side code using clean archit
48
48
 
49
49
  **Reference skills for tech-specific guidance:**
50
50
  - Look in `.claude/skills/` for framework-specific patterns
51
+
52
+ ## Step 1.5: Worktree Check
53
+
54
+ Before implementing, verify your working environment:
55
+
56
+ ```bash
57
+ # Check if AGENTFUL_WORKTREE_DIR is set
58
+ if exists("$AGENTFUL_WORKTREE_DIR"):
59
+ worktree_path = "$AGENTFUL_WORKTREE_DIR"
60
+ echo "✅ Implementing backend in worktree: $worktree_path"
61
+ else:
62
+ echo "📍 Implementing backend in root repository"
63
+ echo "⚠️ Changes will affect main branch directly"
64
+ ```
65
+
66
+ **Report worktree status**: In your final report, always include:
67
+ - Worktree path (if applicable)
68
+ - Branch being worked on
69
+ - Any commits created
51
70
  - Skills contain project-specific conventions, not generic framework docs
52
71
 
53
72
  **Sample existing code to understand conventions:**
@@ -53,6 +53,25 @@ You are the **Frontend Agent**. You implement user interfaces and client-side co
53
53
 
54
54
  **Reference skills for tech-specific guidance:**
55
55
  - Look in `.claude/skills/` for framework-specific patterns
56
+
57
+ ## Step 1.5: Worktree Check
58
+
59
+ Before implementing, verify your working environment:
60
+
61
+ ```bash
62
+ # Check if AGENTFUL_WORKTREE_DIR is set
63
+ if exists("$AGENTFUL_WORKTREE_DIR"):
64
+ worktree_path = "$AGENTFUL_WORKTREE_DIR"
65
+ echo "✅ Implementing frontend in worktree: $worktree_path"
66
+ else:
67
+ echo "📍 Implementing frontend in root repository"
68
+ echo "⚠️ Changes will affect main branch directly"
69
+ ```
70
+
71
+ **Report worktree status**: In your final report, always include:
72
+ - Worktree path (if applicable)
73
+ - Branch being worked on
74
+ - Any commits created
56
75
  - Skills contain project-specific conventions (styling, state management, forms)
57
76
 
58
77
  **Sample existing code to understand conventions:**
@@ -456,6 +456,188 @@ else:
456
456
  }
457
457
  ```
458
458
 
459
+ ## Worktree Management
460
+
461
+ Git worktrees provide isolated environments for parallel agent development. Each task can work in its own worktree without polluting the main repository.
462
+
463
+ ### Why Worktrees?
464
+
465
+ - **Isolation**: Changes are isolated until merged
466
+ - **Parallel Development**: Multiple agents can work simultaneously
467
+ - **Safety**: Experimental changes don't affect main branch
468
+ - **Clean History**: Each worktree has its own git history
469
+
470
+ ### Worktree Modes
471
+
472
+ Controlled via `AGENTFUL_WORKTREE_MODE` environment variable:
473
+
474
+ | Mode | Behavior | When to Use |
475
+ |-------|-----------|--------------|
476
+ | `auto` | Create worktrees automatically | Default, recommended |
477
+ | `block` | Require existing worktree | Strict environments |
478
+ | `off` | Allow direct edits | Legacy, manual control |
479
+
480
+ ### Before Delegation: Worktree Setup
481
+
482
+ Before delegating to any specialist agent:
483
+
484
+ ```bash
485
+ # Check worktree mode
486
+ if AGENTFUL_WORKTREE_MODE != "off":
487
+ # Check if we need a worktree
488
+ current_worktree = get_current_worktree()
489
+
490
+ if not current_worktree:
491
+ # Determine purpose from task type
492
+ purpose = determine_worktree_purpose(task_type)
493
+
494
+ # Create worktree via worktree-service
495
+ worktree = execute("node bin/hooks/worktree-service.js create " + purpose)
496
+
497
+ # Set environment for delegated agents
498
+ # They inherit AGENTFUL_WORKTREE_DIR
499
+ current_worktree = worktree
500
+
501
+ # Track in state.json
502
+ state.current_worktree = {
503
+ name: worktree.name,
504
+ path: worktree.path,
505
+ branch: worktree.branch,
506
+ purpose: worktree.purpose,
507
+ created_at: worktree.created_at
508
+ }
509
+ ```
510
+
511
+ ### Worktree Naming Convention
512
+
513
+ Worktrees are automatically named:
514
+
515
+ ```
516
+ agentful-<purpose>-<branch-slug>-<timestamp>
517
+ ```
518
+
519
+ Examples:
520
+ - `agentful-feature-auth-1739297120` - Feature development
521
+ - `agentful-fix-coverage-1739297150` - Fixer adding coverage
522
+ - `agentful-review-1739297180` - Reviewer validating
523
+ - `agentful-hotfix-login-bug-1739297210` - Hotfix work
524
+
525
+ ### State Schema Extension
526
+
527
+ Track worktrees in `.agentful/state.json`:
528
+
529
+ ```json
530
+ {
531
+ "current_task": "feature/auth",
532
+ "current_phase": "implementation",
533
+ "current_worktree": {
534
+ "name": "agentful-feature-auth-1739297120",
535
+ "path": "/Users/dev/project/.git/worktrees/agentful-feature-auth-1739297120",
536
+ "branch": "feature/auth",
537
+ "purpose": "feature",
538
+ "created_at": "2025-02-11T15:30:00Z"
539
+ },
540
+ "worktrees": {
541
+ "active": [
542
+ {
543
+ "name": "agentful-feature-auth-1739297120",
544
+ "branch": "feature/auth",
545
+ "purpose": "feature",
546
+ "agent": "orchestrator",
547
+ "created_at": "2025-02-11T15:30:00Z",
548
+ "last_activity": "2025-02-11T16:45:00Z"
549
+ }
550
+ ]
551
+ }
552
+ }
553
+ ```
554
+
555
+ ### After Task Completion: Worktree Cleanup
556
+
557
+ When a feature passes all quality gates:
558
+
559
+ ```bash
560
+ if task_completed and AGENTFUL_WORKTREE_AUTO_CLEANUP != "false":
561
+ # Commit changes in worktree
562
+ git -C $WORKTREE_PATH add .
563
+ git -C $WORKTREE_PATH commit -m "feat: complete ${feature_name}"
564
+
565
+ # Ask user what to do next
566
+ response = AskUserQuestion(
567
+ "Feature complete! What would you like to do?",
568
+ options: [
569
+ "Create PR",
570
+ "Merge to main",
571
+ "Keep worktree for review",
572
+ "Clean up worktree"
573
+ ]
574
+ )
575
+
576
+ if response == "Create PR" or response == "Merge to main":
577
+ # Push and create PR/merge
578
+ git -C $WORKTREE_PATH push
579
+ # ... PR creation logic ...
580
+
581
+ # Remove worktree
582
+ execute("node bin/hooks/worktree-service.js remove " + worktree.name)
583
+
584
+ # Update state.json
585
+ state.current_worktree = null
586
+ ```
587
+
588
+ ### Handling Interruptions
589
+
590
+ If user interrupts during worktree operation:
591
+
592
+ ```bash
593
+ on SIGINT:
594
+ if active_worktree:
595
+ # Mark for review instead of deleting
596
+ state.interrupted_worktree = {
597
+ name: active_worktree.name,
598
+ path: active_worktree.path,
599
+ interrupted_at: new Date().toISOString()
600
+ }
601
+
602
+ console.log("Worktree preserved: " + active_worktree.name)
603
+ console.log("Run /agentful-worktree --resume to continue")
604
+ console.log("Run /agentful-worktree --cleanup to remove")
605
+ ```
606
+
607
+ ### Worktree Service API
608
+
609
+ The `worktree-service.js` provides these operations:
610
+
611
+ ```bash
612
+ node bin/hooks/worktree-service.js create <purpose> [branch] # Create worktree
613
+ node bin/hooks/worktree-service.js list # List all worktrees
614
+ node bin/hooks/worktree-service.js status # Show current status
615
+ node bin/hooks/worktree-service.js cleanup # Remove stale worktrees
616
+ node bin/hooks/worktree-service.js prune # Run git prune
617
+ node bin/hooks/worktree-service.js remove <name> # Remove specific worktree
618
+ ```
619
+
620
+ ### Environment Variables
621
+
622
+ | Variable | Default | Description |
623
+ |-----------|----------|-------------|
624
+ | `AGENTFUL_WORKTREE_MODE` | `auto` | Worktree enforcement mode |
625
+ | `AGENTFUL_WORKTREE_DIR` | (auto-set) | Current worktree path |
626
+ | `AGENTFUL_WORKTREE_LOCATION` | `../` | Where to create worktrees |
627
+ | `AGENTFUL_WORKTREE_AUTO_CLEANUP` | `true` | Auto-remove after completion |
628
+ | `AGENTFUL_WORKTREE_RETENTION_DAYS` | `7` | Days before stale cleanup |
629
+ | `AGENTFUL_WORKTREE_MAX_ACTIVE` | `5` | Maximum active worktrees |
630
+
631
+ ### CI/CD Detection
632
+
633
+ Worktree mode auto-disables in CI environments:
634
+
635
+ ```bash
636
+ if process.env.CI == "true" or process.env.GITHUB_ACTIONS == "true":
637
+ # Skip worktree creation
638
+ # CI already provides isolated environments
639
+ ```
640
+
459
641
  ## Delegation Pattern
460
642
 
461
643
  **NEVER implement yourself.** Always use Task tool.
@@ -38,6 +38,24 @@ Fall back to manual Grep if none available
38
38
 
39
39
  **Reference the validation skill** (`.claude/skills/validation/SKILL.md`) for comprehensive validation strategies.
40
40
 
41
+ ## Step 1.5: Worktree Check
42
+
43
+ Before running checks, verify your working environment:
44
+
45
+ ```bash
46
+ # Check if AGENTFUL_WORKTREE_DIR is set
47
+ if exists("$AGENTFUL_WORKTREE_DIR"):
48
+ worktree_path = "$AGENTFUL_WORKTREE_DIR"
49
+ echo "✅ Reviewing in worktree: $worktree_path"
50
+ else:
51
+ echo "📍 Reviewing in root repository"
52
+ echo "⚠️ Validation reports will be saved to main .agentful/"
53
+ ```
54
+
55
+ **Report worktree status**: In your validation report, always include:
56
+ - Worktree path (if applicable)
57
+ - Branch being validated
58
+
41
59
  ## Your Scope
42
60
 
43
61
  - **Type Checking** - Run type checker (tsc, mypy, etc.)
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: agentful-worktree
3
+ description: Manage git worktrees for parallel agent development. List, create, and cleanup worktrees.
4
+ ---
5
+
6
+ # /agentful-worktree
7
+
8
+ Manage git worktrees for parallel agentful development.
9
+
10
+ ## Usage
11
+
12
+ ```bash
13
+ /agentful-worktree # List all active worktrees
14
+ /agentful-worktree --status # Show current session worktree
15
+ /agentful-worktree --create feature/x # Create new worktree for branch
16
+ /agentful-worktree --cleanup # Remove stale worktrees
17
+ /agentful-worktree --prune # Run git worktree prune
18
+ /agentful-worktree --remove <name> # Remove specific worktree
19
+ ```
20
+
21
+ ## Environment Variables
22
+
23
+ | Variable | Default | Description |
24
+ |-----------|----------|-------------|
25
+ | `AGENTFUL_WORKTREE_MODE` | `auto` | `auto` (create), `block` (require), `off` (disabled) |
26
+ | `AGENTFUL_WORKTREE_LOCATION` | `../` | Where to create worktrees (relative to repo) |
27
+ | `AGENTFUL_WORKTREE_AUTO_CLEANUP` | `true` | Auto-remove worktrees after task completion |
28
+ | `AGENTFUL_WORKTREE_RETENTION_DAYS` | `7` | Days before worktrees are marked stale |
29
+ | `AGENTFUL_WORKTREE_MAX_ACTIVE` | `5` | Maximum active worktrees before cleanup forced |
30
+
31
+ ## Examples
32
+
33
+ ### List Worktrees
34
+
35
+ ```
36
+ /agentful-worktree
37
+ ```
38
+
39
+ Output:
40
+ ```
41
+ 📋 Active Worktrees:
42
+
43
+ ┌─────────────────────────────────────────────────────────────┐
44
+ │ agentful-feature-auth-1739297120 │
45
+ │ ├─ Branch: feature/auth │
46
+ │ ├─ Purpose: feature │
47
+ │ ├─ Path: ../agentful-feature-auth-1739297120 │
48
+ │ ├─ Status: 🟢 Active (15 min ago) │
49
+ │ └─ Created: 2025-02-11T15:30:00.000Z │
50
+ └─────────────────────────────────────────────────────────────┘
51
+ ```
52
+
53
+ ### Show Current Status
54
+
55
+ ```
56
+ /agentful-worktree --status
57
+ ```
58
+
59
+ ### Create Worktree
60
+
61
+ ```
62
+ /agentful-worktree --create feature/dashboard
63
+ ```
64
+
65
+ Creates a worktree named `agentful-feature-dashboard-<timestamp>` on the `feature/dashboard` branch.
66
+
67
+ ### Cleanup Stale Worktrees
68
+
69
+ ```
70
+ /agentful-worktree --cleanup
71
+ ```
72
+
73
+ Removes worktrees older than `AGENTFUL_WORKTREE_RETENTION_DAYS` (default: 7 days).
74
+
75
+ ## Worktree Naming Convention
76
+
77
+ Worktrees are automatically named using this pattern:
78
+
79
+ ```
80
+ agentful-<purpose>-<branch-slug>-<timestamp>
81
+ ```
82
+
83
+ Examples:
84
+ - `agentful-feature-auth-1739297120` - Feature development
85
+ - `agentful-fix-coverage-1739297150` - Fixer adding coverage
86
+ - `agentful-review-1739297180` - Reviewer validating
87
+ - `agentful-hotfix-login-bug-1739297210` - Hotfix work
88
+
89
+ ## When to Use Worktrees
90
+
91
+ **Use worktrees when**:
92
+ - Working on multiple features simultaneously
93
+ - Running quality gates while developing other features
94
+ - Reviewing PRs without interrupting active work
95
+ - Testing experimental approaches safely
96
+
97
+ **Don't need worktrees when**:
98
+ - Quick fixes in a single feature branch
99
+ - Documentation-only changes
100
+ - Set `AGENTFUL_WORKTREE_MODE=off` to disable
101
+
102
+ ## See Also
103
+
104
+ - [Git Worktrees Concept](/concepts/git-worktrees) - Deep dive on worktree usage
105
+ - [/agentful-start](/commands/agentful-start) - Main development loop
106
+ - [/agentful-status](/commands/agentful-status) - Check completion progress
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "includeCoAuthoredBy": false,
3
3
  "env": {
4
- "ENABLE_TOOL_SEARCH": "true"
4
+ "ENABLE_TOOL_SEARCH": "true",
5
+ "AGENTFUL_WORKTREE_MODE": "auto"
5
6
  },
6
7
  "hooks": {
7
8
  "SessionStart": [
@@ -23,6 +24,17 @@
23
24
  }
24
25
  ],
25
26
  "PreToolUse": [
27
+ {
28
+ "tools": ["Write", "Edit"],
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "node bin/hooks/ensure-worktree.js",
33
+ "timeout": 10,
34
+ "description": "Ensure agents work in git worktrees (disable with AGENTFUL_WORKTREE_MODE=off)"
35
+ }
36
+ ]
37
+ },
26
38
  {
27
39
  "tools": ["Write", "Edit"],
28
40
  "hooks": [
@@ -199,6 +199,33 @@ The `reviewer` agent runs these checks automatically. The `fixer` agent resolves
199
199
  - Estimated remaining work
200
200
  → If no progress for 2+ minutes, the task may be stuck. Check `.agentful/state.json` for circuit breaker status.
201
201
 
202
+ ### Git Worktree Mode
203
+
204
+ agentful supports automatic git worktree management for safer parallel development.
205
+
206
+ **Modes**:
207
+ | Mode | Behavior |
208
+ |-------|-----------|
209
+ | `auto` | Create worktrees automatically when agents make changes (recommended) |
210
+ | `block` | Require agents to work in existing worktrees |
211
+ | `off` | Allow direct edits to root repository (legacy) |
212
+
213
+ **Enable via environment**:
214
+ ```bash
215
+ export AGENTFUL_WORKTREE_MODE=auto
216
+ ```
217
+
218
+ **Or in `.claude/settings.json`**:
219
+ ```json
220
+ {
221
+ "env": {
222
+ "AGENTFUL_WORKTREE_MODE": "auto"
223
+ }
224
+ }
225
+ ```
226
+
227
+ **More configuration**: See [/agentful-worktree](/commands/agentful-worktree) command and [Git Worktrees concept](/concepts/git-worktrees) for full details.
228
+
202
229
  ## Getting Help
203
230
 
204
231
  **Documentation**: See `.claude/commands/` for detailed command documentation
package/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.7.0"
2
+ "version": "1.8.0"
3
3
  }