specweave 1.0.0-rc.2 → 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.
@@ -45,13 +45,51 @@ export declare const TeamRecommendationSchema: z.ZodObject<{
45
45
  required: z.ZodBoolean;
46
46
  reason: z.ZodString;
47
47
  size: z.ZodString;
48
- skills: z.ZodArray<z.ZodString>;
48
+ skills: z.ZodArray<z.ZodString, "many">;
49
49
  serverlessAlternative: z.ZodOptional<z.ZodObject<{
50
50
  service: z.ZodString;
51
51
  costSavings: z.ZodNumber;
52
- tradeoffs: z.ZodArray<z.ZodString>;
52
+ tradeoffs: z.ZodArray<z.ZodString, "many">;
53
53
  pricingModel: z.ZodOptional<z.ZodString>;
54
- }, z.core.$strip>>;
54
+ }, "strip", z.ZodTypeAny, {
55
+ service?: string;
56
+ costSavings?: number;
57
+ tradeoffs?: string[];
58
+ pricingModel?: string;
59
+ }, {
60
+ service?: string;
61
+ costSavings?: number;
62
+ tradeoffs?: string[];
63
+ pricingModel?: string;
64
+ }>>;
55
65
  priority: z.ZodOptional<z.ZodNumber>;
56
- }, z.core.$strip>;
66
+ }, "strip", z.ZodTypeAny, {
67
+ skills?: string[];
68
+ reason?: string;
69
+ priority?: number;
70
+ size?: string;
71
+ role?: string;
72
+ teamName?: string;
73
+ required?: boolean;
74
+ serverlessAlternative?: {
75
+ service?: string;
76
+ costSavings?: number;
77
+ tradeoffs?: string[];
78
+ pricingModel?: string;
79
+ };
80
+ }, {
81
+ skills?: string[];
82
+ reason?: string;
83
+ priority?: number;
84
+ size?: string;
85
+ role?: string;
86
+ teamName?: string;
87
+ required?: boolean;
88
+ serverlessAlternative?: {
89
+ service?: string;
90
+ costSavings?: number;
91
+ tradeoffs?: string[];
92
+ pricingModel?: string;
93
+ };
94
+ }>;
57
95
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/init/team/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAEhB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IAEpB,oCAAoC;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IAEjB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IAEb,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAElB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IAEf,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB,6CAA6C;IAC7C,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAE9C,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;iBAcnC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/init/team/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAEhB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IAEpB,oCAAoC;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IAEjB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IAEb,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAElB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IAEf,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB,6CAA6C;IAC7C,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAE9C,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcnC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0",
4
4
  "description": "Spec-driven development framework for Claude Code. AI-native workflow with living documentation, intelligent agents, and multilingual support (9 languages). Enterprise-grade traceability with permanent specs and temporary increments.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,26 +1,26 @@
1
- @echo off
2
- REM hook-wrapper.cmd - Windows resilient hook launcher
3
- REM Prevents crashes when dispatcher.mjs is temporarily unavailable
4
-
5
- setlocal enabledelayedexpansion
6
-
7
- set "HOOK_TYPE=%~1"
8
- if "%HOOK_TYPE%"=="" set "HOOK_TYPE=unknown"
9
-
10
- set "SCRIPT_DIR=%~dp0"
11
- set "DISPATCHER=%SCRIPT_DIR%dispatcher.mjs"
12
-
13
- REM Check if dispatcher exists
14
- if not exist "%DISPATCHER%" (
15
- echo {"continue":true,"systemMessage":"Hook skipped: dispatcher.mjs not found"}
16
- exit /b 0
17
- )
18
-
19
- REM Run dispatcher with error suppression
20
- node "%DISPATCHER%" "%HOOK_TYPE%" 2>nul
21
- if errorlevel 1 (
22
- echo {"continue":true,"systemMessage":"Hook error, continuing"}
23
- exit /b 0
24
- )
25
-
26
- exit /b 0
1
+ @echo off
2
+ REM hook-wrapper.cmd - Windows resilient hook launcher
3
+ REM Prevents crashes when dispatcher.mjs is temporarily unavailable
4
+
5
+ setlocal enabledelayedexpansion
6
+
7
+ set "HOOK_TYPE=%~1"
8
+ if "%HOOK_TYPE%"=="" set "HOOK_TYPE=unknown"
9
+
10
+ set "SCRIPT_DIR=%~dp0"
11
+ set "DISPATCHER=%SCRIPT_DIR%dispatcher.mjs"
12
+
13
+ REM Check if dispatcher exists
14
+ if not exist "%DISPATCHER%" (
15
+ echo {"continue":true,"systemMessage":"Hook skipped: dispatcher.mjs not found"}
16
+ exit /b 0
17
+ )
18
+
19
+ REM Run dispatcher with error suppression
20
+ node "%DISPATCHER%" "%HOOK_TYPE%" 2>nul
21
+ if errorlevel 1 (
22
+ echo {"continue":true,"systemMessage":"Hook error, continuing"}
23
+ exit /b 0
24
+ )
25
+
26
+ exit /b 0
@@ -1,16 +1,16 @@
1
- @echo off
2
- :: Universal Session Start Hook for Windows
3
- :: Calls the Node.js dispatcher
4
-
5
- :: Find node.exe
6
- where node >nul 2>&1
7
- if %ERRORLEVEL% neq 0 (
8
- echo {"continue": true, "error": "Node.js not found"}
9
- exit /b 0
10
- )
11
-
12
- :: Get the directory of this script
13
- set "SCRIPT_DIR=%~dp0"
14
-
15
- :: Run the dispatcher
16
- node "%SCRIPT_DIR%dispatcher.mjs" session-start
1
+ @echo off
2
+ :: Universal Session Start Hook for Windows
3
+ :: Calls the Node.js dispatcher
4
+
5
+ :: Find node.exe
6
+ where node >nul 2>&1
7
+ if %ERRORLEVEL% neq 0 (
8
+ echo {"continue": true, "error": "Node.js not found"}
9
+ exit /b 0
10
+ )
11
+
12
+ :: Get the directory of this script
13
+ set "SCRIPT_DIR=%~dp0"
14
+
15
+ :: Run the dispatcher
16
+ node "%SCRIPT_DIR%dispatcher.mjs" session-start
@@ -1,16 +1,16 @@
1
- # Universal Session Start Hook for Windows PowerShell
2
- # Calls the Node.js dispatcher for cross-platform compatibility
3
-
4
- # Find node.exe
5
- $nodePath = Get-Command node -ErrorAction SilentlyContinue
6
-
7
- if (-not $nodePath) {
8
- Write-Host '{"continue": true, "error": "Node.js not found"}'
9
- exit 0
10
- }
11
-
12
- # Get script directory
13
- $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
14
-
15
- # Run the dispatcher
16
- & node "$scriptDir\dispatcher.mjs" session-start
1
+ # Universal Session Start Hook for Windows PowerShell
2
+ # Calls the Node.js dispatcher for cross-platform compatibility
3
+
4
+ # Find node.exe
5
+ $nodePath = Get-Command node -ErrorAction SilentlyContinue
6
+
7
+ if (-not $nodePath) {
8
+ Write-Host '{"continue": true, "error": "Node.js not found"}'
9
+ exit 0
10
+ }
11
+
12
+ # Get script directory
13
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
14
+
15
+ # Run the dispatcher
16
+ & node "$scriptDir\dispatcher.mjs" session-start
@@ -0,0 +1,509 @@
1
+ ---
2
+ name: sw-github:clone
3
+ description: Clone GitHub repositories to local workspace. Use after init if cloning was skipped, to resume interrupted cloning, or to add repos later. Already-cloned repos are automatically skipped.
4
+ ---
5
+
6
+ # Clone GitHub Repositories Command
7
+
8
+ You are a GitHub repository cloning expert. Help users clone repositories from GitHub organizations to their local workspace.
9
+
10
+ ## Purpose
11
+
12
+ This command clones GitHub repositories **after** initial SpecWeave setup (`specweave init`). Use when:
13
+ - User skipped cloning during init
14
+ - **Resuming interrupted cloning** (already-cloned repos are skipped!)
15
+ - Adding repositories from organization
16
+ - Selective cloning with pattern filtering
17
+ - Retrying after partial failures
18
+
19
+ ## CRITICAL: NEVER-STOP BEHAVIOR
20
+
21
+ **This command NEVER stops on individual repo failures!**
22
+ - Each repo failure is logged but cloning continues
23
+ - Already-cloned repos are automatically skipped (resume = re-run!)
24
+ - Final status: `completed` (all success) or `completed_with_warnings` (some failed)
25
+ - Failed repos are listed in result.json for easy retry
26
+
27
+ ## Command Syntax
28
+
29
+ ```bash
30
+ # Interactive mode (prompts for everything)
31
+ /sw-github:clone
32
+
33
+ # Clone from specific org
34
+ /sw-github:clone --org "mycompany"
35
+
36
+ # With pattern filter (glob)
37
+ /sw-github:clone --pattern "api-*"
38
+
39
+ # Regex pattern
40
+ /sw-github:clone --pattern "regex:^frontend-.*$"
41
+
42
+ # Dry-run (preview only)
43
+ /sw-github:clone --dry-run
44
+
45
+ # Resume/retry - just run again! Already cloned repos are skipped
46
+ /sw-github:clone
47
+ ```
48
+
49
+ ## Your Task
50
+
51
+ When the user runs this command:
52
+
53
+ ### Step 1: Check Prerequisites
54
+
55
+ ```typescript
56
+ import { readEnvFile, parseEnvFile } from '../../../src/utils/env-file.js';
57
+ import chalk from 'chalk';
58
+
59
+ const projectPath = process.cwd();
60
+
61
+ // Check for GitHub token
62
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
63
+
64
+ if (!token) {
65
+ // Try .env file
66
+ const envContent = readEnvFile(projectPath);
67
+ if (envContent) {
68
+ const parsed = parseEnvFile(envContent);
69
+ if (parsed.GH_TOKEN || parsed.GITHUB_TOKEN) {
70
+ // Token found in .env
71
+ } else {
72
+ console.log(chalk.red('❌ No GitHub token found.'));
73
+ console.log(chalk.gray(' Set GH_TOKEN or GITHUB_TOKEN environment variable.'));
74
+ console.log(chalk.gray(' Or add to .env file: GH_TOKEN=ghp_xxxx'));
75
+ return;
76
+ }
77
+ } else {
78
+ console.log(chalk.red('❌ No GitHub token found.'));
79
+ console.log(chalk.gray(' Set GH_TOKEN or GITHUB_TOKEN environment variable.'));
80
+ return;
81
+ }
82
+ }
83
+
84
+ console.log(chalk.blue('\n📦 GitHub Repository Cloning\n'));
85
+ console.log(chalk.green(' ✓ GitHub token found'));
86
+ ```
87
+
88
+ ### Step 2: Get Organization
89
+
90
+ ```typescript
91
+ import { input } from '@inquirer/prompts';
92
+
93
+ let org = args.org;
94
+
95
+ if (!org) {
96
+ // Try to detect from config
97
+ const configPath = path.join(projectPath, '.specweave', 'config.json');
98
+ if (fs.existsSync(configPath)) {
99
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
100
+ if (config.github?.org) {
101
+ org = config.github.org;
102
+ console.log(chalk.gray(` Organization: ${org} (from config)`));
103
+ }
104
+ }
105
+
106
+ if (!org) {
107
+ // Try to detect from git remote
108
+ const { execSync } = await import('child_process');
109
+ try {
110
+ const remote = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
111
+ const match = remote.match(/github\.com[:/]([^/]+)/);
112
+ if (match) {
113
+ org = match[1];
114
+ console.log(chalk.gray(` Detected org from git remote: ${org}`));
115
+ }
116
+ } catch {}
117
+ }
118
+
119
+ if (!org) {
120
+ org = await input({
121
+ message: 'Enter GitHub organization or username:',
122
+ validate: v => v.trim() ? true : 'Organization required'
123
+ });
124
+ }
125
+ }
126
+
127
+ console.log(chalk.gray(` Organization: ${org}`));
128
+ ```
129
+
130
+ ### Step 3: Fetch Repositories
131
+
132
+ ```typescript
133
+ console.log(chalk.gray('\n Fetching repositories...'));
134
+
135
+ // Fetch repos from GitHub API with pagination
136
+ const pat = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
137
+ const allRepos = [];
138
+ let page = 1;
139
+ const perPage = 100;
140
+
141
+ while (true) {
142
+ // Try org repos first, fall back to user repos
143
+ let response = await fetch(
144
+ `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?per_page=${perPage}&page=${page}`,
145
+ {
146
+ headers: {
147
+ 'Authorization': `Bearer ${pat}`,
148
+ 'Accept': 'application/vnd.github+json',
149
+ 'X-GitHub-Api-Version': '2022-11-28'
150
+ }
151
+ }
152
+ );
153
+
154
+ if (response.status === 404) {
155
+ // Try as user
156
+ response = await fetch(
157
+ `https://api.github.com/users/${encodeURIComponent(org)}/repos?per_page=${perPage}&page=${page}`,
158
+ {
159
+ headers: {
160
+ 'Authorization': `Bearer ${pat}`,
161
+ 'Accept': 'application/vnd.github+json',
162
+ 'X-GitHub-Api-Version': '2022-11-28'
163
+ }
164
+ }
165
+ );
166
+ }
167
+
168
+ if (!response.ok) {
169
+ console.log(chalk.red(`❌ Failed to fetch repos: ${response.status}`));
170
+ const error = await response.text();
171
+ console.log(chalk.gray(` ${error}`));
172
+ return;
173
+ }
174
+
175
+ const batch = await response.json();
176
+ if (batch.length === 0) break;
177
+
178
+ allRepos.push(...batch);
179
+
180
+ if (batch.length < perPage) break;
181
+ page++;
182
+
183
+ // Progress for large orgs
184
+ if (allRepos.length % 100 === 0) {
185
+ console.log(chalk.gray(` Fetched ${allRepos.length} repos...`));
186
+ }
187
+ }
188
+
189
+ if (allRepos.length === 0) {
190
+ console.log(chalk.yellow('\n⚠️ No repositories found.'));
191
+ return;
192
+ }
193
+
194
+ console.log(chalk.green(` ✓ Found ${allRepos.length} repositories in ${org}`));
195
+ ```
196
+
197
+ ### Step 4: Check Already Cloned (Resume Detection)
198
+
199
+ ```typescript
200
+ import * as fs from 'fs';
201
+ import * as path from 'path';
202
+
203
+ // Check which repos already exist
204
+ const alreadyCloned = [];
205
+ const needsCloning = [];
206
+
207
+ for (const repo of allRepos) {
208
+ const repoPath = path.join(projectPath, repo.name, '.git');
209
+ if (fs.existsSync(repoPath)) {
210
+ alreadyCloned.push(repo);
211
+ } else {
212
+ needsCloning.push(repo);
213
+ }
214
+ }
215
+
216
+ if (alreadyCloned.length > 0) {
217
+ console.log(chalk.cyan(`\n 📂 Already cloned: ${alreadyCloned.length} repos (will be skipped)`));
218
+ if (alreadyCloned.length <= 5) {
219
+ alreadyCloned.forEach(r => console.log(chalk.gray(` ✓ ${r.name}`)));
220
+ } else {
221
+ alreadyCloned.slice(0, 3).forEach(r => console.log(chalk.gray(` ✓ ${r.name}`)));
222
+ console.log(chalk.gray(` ... and ${alreadyCloned.length - 3} more`));
223
+ }
224
+ }
225
+
226
+ console.log(chalk.blue(`\n 📦 Need to clone: ${needsCloning.length} repos`));
227
+ ```
228
+
229
+ ### Step 5: Apply Pattern Filter
230
+
231
+ ```typescript
232
+ import { filterRepositoriesByPattern } from '../../../src/cli/helpers/selection-strategy.js';
233
+ import { select, input } from '@inquirer/prompts';
234
+
235
+ let filteredRepos = needsCloning;
236
+ let patternDescription = 'all';
237
+
238
+ if (args.pattern) {
239
+ const isRegex = args.pattern.startsWith('regex:');
240
+ const pattern = isRegex ? args.pattern.slice(6) : args.pattern;
241
+
242
+ const clonePattern = {
243
+ strategy: isRegex ? 'pattern-regex' : 'pattern-glob',
244
+ pattern: pattern,
245
+ isRegex
246
+ };
247
+
248
+ filteredRepos = filterRepositoriesByPattern(needsCloning, clonePattern);
249
+ patternDescription = `matching "${pattern}"`;
250
+
251
+ console.log(chalk.gray(` Pattern: ${args.pattern}`));
252
+ console.log(chalk.gray(` Matched: ${filteredRepos.length} of ${needsCloning.length} repos\n`));
253
+ } else if (needsCloning.length > 0) {
254
+ const strategy = await select({
255
+ message: 'How do you want to select repositories?',
256
+ choices: [
257
+ { name: 'All - Clone all repositories', value: 'all' },
258
+ { name: 'Pattern (glob) - e.g., "api-*", "*-backend"', value: 'pattern-glob' },
259
+ { name: 'Pattern (regex) - e.g., "^api-.*$"', value: 'pattern-regex' }
260
+ ]
261
+ });
262
+
263
+ if (strategy !== 'all') {
264
+ const pattern = await input({
265
+ message: 'Enter pattern:',
266
+ validate: v => v.trim() ? true : 'Pattern required'
267
+ });
268
+
269
+ const clonePattern = {
270
+ strategy,
271
+ pattern: pattern.trim(),
272
+ isRegex: strategy === 'pattern-regex'
273
+ };
274
+
275
+ filteredRepos = filterRepositoriesByPattern(needsCloning, clonePattern);
276
+ patternDescription = `matching "${pattern.trim()}"`;
277
+ }
278
+ }
279
+
280
+ if (filteredRepos.length === 0) {
281
+ if (alreadyCloned.length > 0) {
282
+ console.log(chalk.green(`\n✅ All ${alreadyCloned.length} repos already cloned. Nothing to do!`));
283
+ } else {
284
+ console.log(chalk.yellow(`\n⚠️ No repositories ${patternDescription}.`));
285
+ }
286
+ return;
287
+ }
288
+ ```
289
+
290
+ ### Step 6: Preview and Confirm
291
+
292
+ ```typescript
293
+ import { confirm } from '@inquirer/prompts';
294
+
295
+ console.log(chalk.blue(`\n📦 Repositories to clone (${filteredRepos.length}):\n`));
296
+
297
+ // Show preview (max 20)
298
+ filteredRepos.slice(0, 20).forEach(repo => {
299
+ console.log(chalk.gray(` • ${repo.name}`));
300
+ });
301
+
302
+ if (filteredRepos.length > 20) {
303
+ console.log(chalk.gray(` ... and ${filteredRepos.length - 20} more\n`));
304
+ }
305
+
306
+ if (args.dryRun) {
307
+ console.log(chalk.cyan('\n🔎 DRY RUN: No repositories will be cloned.\n'));
308
+ console.log(chalk.gray(' Remove --dry-run to actually clone.'));
309
+ return;
310
+ }
311
+
312
+ const confirmed = await confirm({
313
+ message: `Clone ${filteredRepos.length} repositories to current directory?`,
314
+ default: true
315
+ });
316
+
317
+ if (!confirmed) {
318
+ console.log(chalk.gray('\n⏭️ Cloning cancelled.\n'));
319
+ return;
320
+ }
321
+ ```
322
+
323
+ ### Step 7: Start Background Cloning
324
+
325
+ ```typescript
326
+ import { triggerGitHubRepoCloning } from '../../../src/cli/helpers/init/github-repo-cloning.js';
327
+
328
+ // Build selection
329
+ const githubRepoSelection = {
330
+ org,
331
+ pat: process.env.GH_TOKEN || process.env.GITHUB_TOKEN
332
+ };
333
+
334
+ // Build clonePattern
335
+ const clonePatternResult = args.pattern
336
+ ? {
337
+ strategy: args.pattern.startsWith('regex:') ? 'pattern-regex' : 'pattern-glob',
338
+ pattern: args.pattern.startsWith('regex:') ? args.pattern.slice(6) : args.pattern
339
+ }
340
+ : { strategy: 'all' };
341
+
342
+ // Trigger background cloning
343
+ const jobId = await triggerGitHubRepoCloning(projectPath, githubRepoSelection, clonePatternResult);
344
+
345
+ if (jobId) {
346
+ console.log(chalk.green('\n✅ Clone job started successfully!\n'));
347
+ console.log(chalk.blue('📋 Key Points:'));
348
+ console.log(chalk.gray(' • Cloning runs in background - you can continue working'));
349
+ console.log(chalk.gray(' • Already-cloned repos are automatically skipped'));
350
+ console.log(chalk.gray(' • Individual failures do NOT stop the job'));
351
+ console.log(chalk.gray(' • To resume after interruption: just run /sw-github:clone again!\n'));
352
+
353
+ console.log(chalk.blue('🔧 Commands:'));
354
+ console.log(chalk.cyan(` /sw:jobs → Check progress`));
355
+ console.log(chalk.cyan(` /sw:jobs --follow ${jobId.slice(0, 8)} → Follow live`));
356
+ console.log(chalk.cyan(` /sw:jobs --logs ${jobId.slice(0, 8)} → View logs`));
357
+ }
358
+ ```
359
+
360
+ ## Examples
361
+
362
+ ### Example 1: Fresh Clone
363
+ **User**: `/sw-github:clone --org olympusnova`
364
+
365
+ **Output**:
366
+ ```
367
+ 📦 GitHub Repository Cloning
368
+
369
+ ✓ GitHub token found
370
+ Organization: olympusnova
371
+
372
+ Fetching repositories...
373
+ ✓ Found 512 repositories in olympusnova
374
+
375
+ 📦 Need to clone: 512 repos
376
+
377
+ How do you want to select repositories?
378
+ > All - Clone all repositories
379
+
380
+ 📦 Repositories to clone (512):
381
+
382
+ • api-gateway
383
+ • frontend-web
384
+ • mobile-app
385
+ ... and 509 more
386
+
387
+ Clone 512 repositories to current directory? (Y/n)
388
+
389
+ 🔄 Starting background clone for 512 repositories...
390
+
391
+ ✓ Clone job started in background (PID: 12345)
392
+
393
+ ✅ Clone job started successfully!
394
+
395
+ 📋 Key Points:
396
+ • Cloning runs in background - you can continue working
397
+ • Already-cloned repos are automatically skipped
398
+ • Individual failures do NOT stop the job
399
+ • To resume after interruption: just run /sw-github:clone again!
400
+
401
+ 🔧 Commands:
402
+ /sw:jobs → Check progress
403
+ /sw:jobs --follow abc12345 → Follow live
404
+ /sw:jobs --logs abc12345 → View logs
405
+ ```
406
+
407
+ ### Example 2: Resume After Interruption
408
+ **User**: `/sw-github:clone` (after previous job was interrupted at 87/512)
409
+
410
+ **Output**:
411
+ ```
412
+ 📦 GitHub Repository Cloning
413
+
414
+ ✓ GitHub token found
415
+ Organization: olympusnova (from config)
416
+
417
+ Fetching repositories...
418
+ ✓ Found 512 repositories in olympusnova
419
+
420
+ 📂 Already cloned: 87 repos (will be skipped)
421
+ ✓ api-gateway
422
+ ✓ frontend-web
423
+ ✓ mobile-app
424
+ ... and 84 more
425
+
426
+ 📦 Need to clone: 425 repos
427
+
428
+ Clone 425 repositories to current directory? (Y/n)
429
+
430
+ 🔄 Resuming clone for 425 remaining repositories...
431
+ ```
432
+
433
+ ### Example 3: Pattern Filter
434
+ **User**: `/sw-github:clone --pattern "api-*"`
435
+
436
+ **Output**:
437
+ ```
438
+ 📦 GitHub Repository Cloning
439
+
440
+ Pattern: api-*
441
+ Matched: 45 of 512 repos
442
+
443
+ 📦 Repositories to clone (45):
444
+
445
+ • api-gateway
446
+ • api-auth
447
+ • api-payments
448
+ ...
449
+ ```
450
+
451
+ ### Example 4: Dry Run
452
+ **User**: `/sw-github:clone --dry-run`
453
+
454
+ **Output**:
455
+ ```
456
+ 📦 Repositories to clone (512):
457
+
458
+ • api-gateway
459
+ • frontend-web
460
+ ...
461
+
462
+ 🔎 DRY RUN: No repositories will be cloned.
463
+ Remove --dry-run to actually clone.
464
+ ```
465
+
466
+ ## Important Notes
467
+
468
+ ### Resume = Just Re-run!
469
+ **There's no special resume command.** Just run `/sw-github:clone` again:
470
+ - Already-cloned repos are detected via `.git` folder
471
+ - They're automatically skipped (instant, no API calls)
472
+ - Only remaining repos are queued for cloning
473
+
474
+ ### Never Stops on Failure
475
+ - Each repo cloned independently
476
+ - Failures logged but don't stop the job
477
+ - Final status: `completed_with_warnings` if any failed
478
+ - Failed repo list saved to `result.json`
479
+
480
+ ### Retry Failed Repos
481
+ After job completes with warnings:
482
+ ```bash
483
+ # Check which repos failed
484
+ cat .specweave/state/jobs/<jobId>/result.json
485
+
486
+ # Just run clone again - it skips successful ones!
487
+ /sw-github:clone
488
+ ```
489
+
490
+ ## Related Commands
491
+
492
+ - `/sw:init` - Initial SpecWeave setup (includes repo cloning option)
493
+ - `/sw:jobs` - Monitor background jobs
494
+ - `/sw:jobs --follow <id>` - Watch progress live
495
+ - `/sw-github:sync` - Sync with GitHub Issues
496
+
497
+ ## Error Handling
498
+
499
+ | Error | Cause | Solution |
500
+ |-------|-------|----------|
501
+ | No GitHub token | Missing env var | Set `GH_TOKEN` or `GITHUB_TOKEN` |
502
+ | 401 Unauthorized | Invalid token | Check token has `repo` scope |
503
+ | 404 Not Found | Wrong org name | Verify organization/username |
504
+ | Rate limit | Too many API calls | Wait and retry, or use pagination |
505
+ | Clone failure | Permission/network | Job continues, repo listed in failures |
506
+
507
+ ---
508
+
509
+ **Multi-Repo Support**: Perfect for microservices architectures with many GitHub repositories.