specweave 0.15.1 → 0.16.2

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 (52) hide show
  1. package/CLAUDE.md +38 -0
  2. package/dist/cli/commands/init.d.ts.map +1 -1
  3. package/dist/cli/commands/init.js +162 -3
  4. package/dist/cli/commands/init.js.map +1 -1
  5. package/dist/cli/helpers/github/increment-profile-selector.d.ts +47 -0
  6. package/dist/cli/helpers/github/increment-profile-selector.d.ts.map +1 -0
  7. package/dist/cli/helpers/github/increment-profile-selector.js +186 -0
  8. package/dist/cli/helpers/github/increment-profile-selector.js.map +1 -0
  9. package/dist/cli/helpers/github/profile-manager.d.ts +119 -0
  10. package/dist/cli/helpers/github/profile-manager.d.ts.map +1 -0
  11. package/dist/cli/helpers/github/profile-manager.js +311 -0
  12. package/dist/cli/helpers/github/profile-manager.js.map +1 -0
  13. package/dist/cli/helpers/issue-tracker/github-multi-repo.d.ts +81 -0
  14. package/dist/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -0
  15. package/dist/cli/helpers/issue-tracker/github-multi-repo.js +385 -0
  16. package/dist/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -0
  17. package/dist/cli/helpers/issue-tracker/github.d.ts +13 -0
  18. package/dist/cli/helpers/issue-tracker/github.d.ts.map +1 -1
  19. package/dist/cli/helpers/issue-tracker/github.js +38 -143
  20. package/dist/cli/helpers/issue-tracker/github.js.map +1 -1
  21. package/dist/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  22. package/dist/cli/helpers/issue-tracker/index.js +126 -43
  23. package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
  24. package/dist/cli/helpers/issue-tracker/utils.d.ts +8 -0
  25. package/dist/cli/helpers/issue-tracker/utils.d.ts.map +1 -1
  26. package/dist/cli/helpers/issue-tracker/utils.js +46 -0
  27. package/dist/cli/helpers/issue-tracker/utils.js.map +1 -1
  28. package/dist/core/increment/active-increment-manager.d.ts +79 -0
  29. package/dist/core/increment/active-increment-manager.d.ts.map +1 -0
  30. package/dist/core/increment/active-increment-manager.js +153 -0
  31. package/dist/core/increment/active-increment-manager.js.map +1 -0
  32. package/dist/core/increment/metadata-manager.d.ts +2 -0
  33. package/dist/core/increment/metadata-manager.d.ts.map +1 -1
  34. package/dist/core/increment/metadata-manager.js +15 -0
  35. package/dist/core/increment/metadata-manager.js.map +1 -1
  36. package/dist/utils/git-detector.d.ts +84 -0
  37. package/dist/utils/git-detector.d.ts.map +1 -0
  38. package/dist/utils/git-detector.js +233 -0
  39. package/dist/utils/git-detector.js.map +1 -0
  40. package/package.json +2 -2
  41. package/plugins/specweave/commands/specweave-done.md +109 -1
  42. package/plugins/specweave/hooks/lib/update-status-line.sh +30 -4
  43. package/plugins/specweave/hooks/post-increment-planning.sh +50 -5
  44. package/plugins/specweave/hooks/user-prompt-submit.sh +77 -21
  45. package/plugins/specweave/skills/increment-planner/SKILL.md +12 -5
  46. package/plugins/specweave/skills/increment-planner/scripts/feature-utils.js +26 -5
  47. package/plugins/specweave-ado/skills/ado-sync/SKILL.md +2 -2
  48. package/plugins/specweave-figma/ARCHITECTURE.md +1 -1
  49. package/plugins/specweave-figma/README.md +1 -1
  50. package/plugins/specweave-ml/README.md +1 -1
  51. package/src/templates/CLAUDE.md.template +8 -9
  52. package/plugins/specweave-github/hooks/post-increment-done.sh +0 -224
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Git Remote Detection Utilities
3
+ *
4
+ * Detects and parses git remotes to auto-configure repository settings
5
+ *
6
+ * @module utils/git-detector
7
+ */
8
+ import { execFileNoThrowSync } from './execFileNoThrow.js';
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ /**
12
+ * Parse a git URL to extract provider, owner, and repo
13
+ *
14
+ * Supports:
15
+ * - HTTPS: https://github.com/owner/repo.git
16
+ * - SSH: git@github.com:owner/repo.git
17
+ * - SSH with protocol: ssh://git@github.com/owner/repo.git
18
+ * - GitHub Enterprise: https://github.company.com/owner/repo.git
19
+ *
20
+ * @param url - Git remote URL
21
+ * @returns Parsed remote information
22
+ */
23
+ export function parseGitUrl(url) {
24
+ const result = {
25
+ url,
26
+ provider: 'unknown'
27
+ };
28
+ // Remove trailing .git if present
29
+ const cleanUrl = url.replace(/\.git$/, '');
30
+ // GitHub patterns
31
+ const githubPatterns = [
32
+ // HTTPS: https://github.com/owner/repo
33
+ /^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)$/,
34
+ // SSH: git@github.com:owner/repo
35
+ /^git@github\.com:([^\/]+)\/([^\/]+)$/,
36
+ // SSH with protocol: ssh://git@github.com/owner/repo
37
+ /^ssh:\/\/git@github\.com\/([^\/]+)\/([^\/]+)$/,
38
+ // GitHub Enterprise: https://github.company.com/owner/repo
39
+ /^https?:\/\/github\.[^\/]+\/([^\/]+)\/([^\/]+)$/,
40
+ // GitHub Enterprise SSH: git@github.company.com:owner/repo
41
+ /^git@github\.[^:]+:([^\/]+)\/([^\/]+)$/
42
+ ];
43
+ for (const pattern of githubPatterns) {
44
+ const match = cleanUrl.match(pattern);
45
+ if (match) {
46
+ result.provider = 'github';
47
+ result.owner = match[1];
48
+ result.repo = match[2];
49
+ return result;
50
+ }
51
+ }
52
+ // GitLab patterns
53
+ const gitlabPatterns = [
54
+ // HTTPS: https://gitlab.com/owner/repo
55
+ /^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)$/,
56
+ // SSH: git@gitlab.com:owner/repo
57
+ /^git@gitlab\.com:([^\/]+)\/([^\/]+)$/,
58
+ // Self-hosted GitLab
59
+ /^https?:\/\/gitlab\.[^\/]+\/([^\/]+)\/([^\/]+)$/,
60
+ /^git@gitlab\.[^:]+:([^\/]+)\/([^\/]+)$/
61
+ ];
62
+ for (const pattern of gitlabPatterns) {
63
+ const match = cleanUrl.match(pattern);
64
+ if (match) {
65
+ result.provider = 'gitlab';
66
+ result.owner = match[1];
67
+ result.repo = match[2];
68
+ return result;
69
+ }
70
+ }
71
+ // Bitbucket patterns
72
+ const bitbucketPatterns = [
73
+ // HTTPS: https://bitbucket.org/owner/repo
74
+ /^https?:\/\/bitbucket\.org\/([^\/]+)\/([^\/]+)$/,
75
+ // SSH: git@bitbucket.org:owner/repo
76
+ /^git@bitbucket\.org:([^\/]+)\/([^\/]+)$/
77
+ ];
78
+ for (const pattern of bitbucketPatterns) {
79
+ const match = cleanUrl.match(pattern);
80
+ if (match) {
81
+ result.provider = 'bitbucket';
82
+ result.owner = match[1];
83
+ result.repo = match[2];
84
+ return result;
85
+ }
86
+ }
87
+ // Azure DevOps patterns
88
+ const azurePatterns = [
89
+ // HTTPS: https://dev.azure.com/org/project/_git/repo
90
+ /^https?:\/\/dev\.azure\.com\/([^\/]+)\/[^\/]+\/_git\/([^\/]+)$/,
91
+ // SSH: git@ssh.dev.azure.com:v3/org/project/repo
92
+ /^git@ssh\.dev\.azure\.com:v3\/([^\/]+)\/[^\/]+\/([^\/]+)$/,
93
+ // Old visualstudio.com format
94
+ /^https?:\/\/([^\.]+)\.visualstudio\.com\/[^\/]+\/_git\/([^\/]+)$/
95
+ ];
96
+ for (const pattern of azurePatterns) {
97
+ const match = cleanUrl.match(pattern);
98
+ if (match) {
99
+ result.provider = 'azure';
100
+ result.owner = match[1];
101
+ result.repo = match[2];
102
+ return result;
103
+ }
104
+ }
105
+ return result;
106
+ }
107
+ /**
108
+ * Detect git remotes in a project directory
109
+ *
110
+ * @param projectPath - Path to the project directory
111
+ * @returns Detection result with all remotes
112
+ */
113
+ export async function detectGitRemotes(projectPath) {
114
+ // Check if .git directory exists
115
+ const gitDir = path.join(projectPath, '.git');
116
+ if (!fs.existsSync(gitDir)) {
117
+ return {
118
+ remotes: [],
119
+ hasGit: false,
120
+ error: 'Not a git repository'
121
+ };
122
+ }
123
+ // Run git remote -v to get all remotes
124
+ const result = execFileNoThrowSync('git', ['remote', '-v'], {
125
+ cwd: projectPath
126
+ });
127
+ if (!result.success) {
128
+ return {
129
+ remotes: [],
130
+ hasGit: true,
131
+ error: result.stderr || 'Failed to get git remotes'
132
+ };
133
+ }
134
+ // Parse the output
135
+ const lines = (result.stdout || '').split('\n').filter(line => line.trim());
136
+ const remotesMap = new Map();
137
+ for (const line of lines) {
138
+ // Format: origin https://github.com/owner/repo.git (fetch)
139
+ // Format: origin git@github.com:owner/repo.git (push)
140
+ const match = line.match(/^(\S+)\s+(\S+)\s+\((fetch|push)\)$/);
141
+ if (match) {
142
+ const [, name, url, type] = match;
143
+ // We only care about fetch URLs (avoid duplicates)
144
+ if (type === 'fetch') {
145
+ const parsedInfo = parseGitUrl(url);
146
+ const remote = {
147
+ name,
148
+ url,
149
+ provider: parsedInfo.provider || 'unknown',
150
+ owner: parsedInfo.owner,
151
+ repo: parsedInfo.repo
152
+ };
153
+ remotesMap.set(name, remote);
154
+ }
155
+ }
156
+ }
157
+ return {
158
+ remotes: Array.from(remotesMap.values()),
159
+ hasGit: true
160
+ };
161
+ }
162
+ /**
163
+ * Get GitHub remotes only
164
+ *
165
+ * @param projectPath - Path to the project directory
166
+ * @returns Array of GitHub remotes
167
+ */
168
+ export async function detectGitHubRemotes(projectPath) {
169
+ const result = await detectGitRemotes(projectPath);
170
+ return result.remotes.filter(r => r.provider === 'github');
171
+ }
172
+ /**
173
+ * Detect primary GitHub remote (prefer 'origin')
174
+ *
175
+ * @param projectPath - Path to the project directory
176
+ * @returns Primary GitHub remote or null
177
+ */
178
+ export async function detectPrimaryGitHubRemote(projectPath) {
179
+ const githubRemotes = await detectGitHubRemotes(projectPath);
180
+ if (githubRemotes.length === 0) {
181
+ return null;
182
+ }
183
+ // Prefer 'origin' if it exists
184
+ const origin = githubRemotes.find(r => r.name === 'origin');
185
+ if (origin) {
186
+ return origin;
187
+ }
188
+ // Otherwise return the first GitHub remote
189
+ return githubRemotes[0];
190
+ }
191
+ /**
192
+ * Check if project has multiple GitHub remotes
193
+ *
194
+ * @param projectPath - Path to the project directory
195
+ * @returns True if multiple GitHub remotes exist
196
+ */
197
+ export async function hasMultipleGitHubRemotes(projectPath) {
198
+ const githubRemotes = await detectGitHubRemotes(projectPath);
199
+ return githubRemotes.length > 1;
200
+ }
201
+ /**
202
+ * Format git remote for display
203
+ *
204
+ * @param remote - Git remote to format
205
+ * @returns Formatted string for display
206
+ */
207
+ export function formatGitRemote(remote) {
208
+ if (remote.owner && remote.repo) {
209
+ return `${remote.name}: ${remote.owner}/${remote.repo} (${remote.provider})`;
210
+ }
211
+ return `${remote.name}: ${remote.url} (${remote.provider})`;
212
+ }
213
+ /**
214
+ * Get unique repository identifiers from remotes
215
+ *
216
+ * @param remotes - Array of git remotes
217
+ * @returns Array of unique owner/repo combinations
218
+ */
219
+ export function getUniqueRepositories(remotes) {
220
+ const seen = new Set();
221
+ const unique = [];
222
+ for (const remote of remotes) {
223
+ if (remote.owner && remote.repo) {
224
+ const key = `${remote.owner}/${remote.repo}`;
225
+ if (!seen.has(key)) {
226
+ seen.add(key);
227
+ unique.push({ owner: remote.owner, repo: remote.repo });
228
+ }
229
+ }
230
+ }
231
+ return unique;
232
+ }
233
+ //# sourceMappingURL=git-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-detector.js","sourceRoot":"","sources":["../../src/utils/git-detector.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specweave",
3
- "version": "0.15.1",
3
+ "version": "0.16.2",
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",
@@ -17,7 +17,7 @@
17
17
  "test:unit": "jest tests/unit --coverage",
18
18
  "test:integration": "jest tests/integration --coverage",
19
19
  "test:smoke": "bash tests/smoke/smoke-test.sh",
20
- "test:e2e": "playwright test tests/e2e/ --grep-invert=\"(should default to claude adapter|should use claude adapter when explicitly requested|should use generic adapter|should create .claude|should initialize project with specweave init|should create correct directory structure|should handle non-interactive mode correctly|should validate config.json structure|should create .specweave directory structure|should create CLAUDE.md and AGENTS.md|should initialize git repository|should install SpecWeave|should scaffold SaaS|should create proper directory|should create required configuration|should install core skills|should install core agents|should have deployment|should have Stripe|ADO Sync|Increment Discipline Blocking|Self-Reflection)\"",
20
+ "test:e2e": "playwright test tests/e2e/ --grep-invert=\"(should default to claude adapter|should use claude adapter when explicitly requested|should use generic adapter|should create .claude|should initialize project with specweave init|should create correct directory structure|should handle non-interactive mode correctly|should validate config.json structure|should create .specweave directory structure|should create CLAUDE.md and AGENTS.md|should initialize git repository|should install SpecWeave|should scaffold SaaS|should create proper directory|should create required configuration|should install core skills|should install core agents|should have deployment|should have Stripe|ADO Sync|Increment Discipline Blocking|Self-Reflection|Increment Discipline Enforcement)\"",
21
21
  "test:all": "npm run test:unit && npm run test:integration && npm run test:e2e",
22
22
  "test:coverage": "jest --coverage --coverageReporters=text --coverageReporters=lcov",
23
23
  "test": "npm run test:smoke && npm run test:e2e",
@@ -312,8 +312,116 @@ Closing increment 0001-user-authentication...
312
312
  ✓ Updated backlog (4 P3 tasks moved)
313
313
 
314
314
  🎉 Increment 0001 closed successfully!
315
+ ```
316
+
317
+ ### Step 4: Post-Closure Sync (AUTOMATIC)
318
+
319
+ **CRITICAL**: After increment closes, automatically perform these syncs:
320
+
321
+ #### A) Sync Living Docs to GitHub Project
322
+
323
+ **Check configuration** (`.specweave/config.json`):
324
+ ```typescript
325
+ // Check if GitHub sync is enabled
326
+ const syncEnabled = config.hooks?.post_increment_done?.sync_to_github_project === true;
327
+ ```
328
+
329
+ **If enabled**:
330
+ 1. **Find living docs spec**:
331
+ - Look for `.specweave/docs/internal/specs/spec-{id}*.md`
332
+ - Pattern 1: `spec-0001-user-authentication.md` (4-digit)
333
+ - Pattern 2: `spec-001-user-authentication.md` (3-digit)
334
+ - Check increment `spec.md` for reference
335
+
336
+ 2. **Sync to GitHub Project**:
337
+ ```bash
338
+ /specweave-github:sync-spec <spec-file>
339
+ ```
340
+
341
+ 3. **Report result**:
342
+ ```
343
+ 🔗 Post-Closure Sync:
344
+ ✓ Found living docs: spec-0001-user-authentication.md
345
+ ✓ Syncing to GitHub Project...
346
+ ✓ GitHub Project updated successfully
347
+ ```
348
+
349
+ **If spec not found**:
350
+ ```
351
+ ℹ️ No living docs spec found (OK for bug/hotfix increments)
352
+ ```
353
+
354
+ **If sync disabled**:
355
+ ```
356
+ ℹ️ GitHub Project sync disabled in config
357
+ ```
358
+
359
+ #### B) Close GitHub Issue (if exists)
360
+
361
+ **Check metadata** (`.specweave/increments/0001/.metadata.json`):
362
+ ```json
363
+ {
364
+ "github": {
365
+ "issue": 42,
366
+ "url": "https://github.com/org/repo/issues/42"
367
+ }
368
+ }
369
+ ```
370
+
371
+ **If issue exists AND config.hooks.post_increment_done.close_github_issue = true**:
372
+ 1. **Close issue via gh CLI**:
373
+ ```bash
374
+ gh issue close 42 --comment "✅ Increment 0001 completed and closed
375
+
376
+ All PM gates passed:
377
+ ✅ Gate 1: Tasks completed
378
+ ✅ Gate 2: Tests passing
379
+ ✅ Gate 3: Documentation updated
380
+
381
+ Duration: 14 days
382
+ Velocity: +50% faster than planned"
383
+ ```
384
+
385
+ 2. **Report result**:
386
+ ```
387
+ 🐙 GitHub Issue:
388
+ ✓ Closed issue #42
389
+ ✓ Added completion summary
390
+ ```
391
+
392
+ **If no issue**:
393
+ ```
394
+ ℹ️ No GitHub issue linked to this increment
395
+ ```
396
+
397
+ **Example Full Output**:
398
+ ```
399
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
400
+ 🎉 INCREMENT CLOSED SUCCESSFULLY
401
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
402
+
403
+ Increment: 0001-user-authentication
404
+ Status: completed
405
+ Duration: 14 days (vs 21 estimated)
406
+ Velocity: +50% faster
407
+
408
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
409
+ 🔗 POST-CLOSURE SYNC
410
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
411
+
412
+ GitHub Project:
413
+ ✓ Found living docs: spec-0001-user-authentication.md
414
+ ✓ Syncing to GitHub Project...
415
+ ✓ GitHub Project updated successfully
416
+
417
+ GitHub Issue:
418
+ ✓ Closed issue #42
419
+ ✓ Added completion summary
420
+
421
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
422
+ 📋 NEXT STEPS
423
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
315
424
 
316
- Next steps:
317
425
  1. Create PR: git push && gh pr create
318
426
  2. Deploy to staging: npm run deploy:staging
319
427
  3. Create new increment: /specweave:increment "Next feature"
@@ -76,8 +76,23 @@ else
76
76
  fi
77
77
 
78
78
  # Parse tasks.md (THIS is the slow part: 10-50ms)
79
- TOTAL_TASKS=$(grep -c '^## T-' "$TASKS_FILE" 2>/dev/null || echo 0)
80
- COMPLETED_TASKS=$(grep -c '^\[x\]' "$TASKS_FILE" 2>/dev/null || echo 0)
79
+ # Support both ## T- and ### T- formats (flexible task heading levels)
80
+ TOTAL_TASKS=$(grep -cE '^##+ T-' "$TASKS_FILE" 2>/dev/null || echo 0)
81
+
82
+ # Remove any whitespace/newlines and ensure integer
83
+ TOTAL_TASKS=$(echo "$TOTAL_TASKS" | tr -d '\n\r ' | grep -E '^[0-9]+$' || echo 0)
84
+
85
+ # Support both checkbox formats:
86
+ # 1. Standard: [x] at line start
87
+ # 2. Inline: **Status**: [x] (in task body)
88
+ COMPLETED_TASKS_STANDARD=$(grep -c '^\[x\]' "$TASKS_FILE" 2>/dev/null || echo 0)
89
+ COMPLETED_TASKS_INLINE=$(grep -c 'Status\*\*: \[x\]' "$TASKS_FILE" 2>/dev/null || echo 0)
90
+
91
+ # Remove any whitespace/newlines and ensure integer
92
+ COMPLETED_TASKS_STANDARD=$(echo "$COMPLETED_TASKS_STANDARD" | tr -d '\n\r ' | grep -E '^[0-9]+$' || echo 0)
93
+ COMPLETED_TASKS_INLINE=$(echo "$COMPLETED_TASKS_INLINE" | tr -d '\n\r ' | grep -E '^[0-9]+$' || echo 0)
94
+
95
+ COMPLETED_TASKS=$((COMPLETED_TASKS_STANDARD + COMPLETED_TASKS_INLINE))
81
96
 
82
97
  # Calculate percentage
83
98
  if [[ "$TOTAL_TASKS" -gt 0 ]]; then
@@ -87,8 +102,19 @@ else
87
102
  fi
88
103
 
89
104
  # Find current task (first incomplete task)
90
- # Strategy: Find first [ ] checkbox, then get the task heading above it
91
- CURRENT_TASK_LINE=$(grep -B1 '^\[ \]' "$TASKS_FILE" 2>/dev/null | grep '^## T-' | head -1 || echo "")
105
+ # Strategy: Find first [ ] checkbox (either format), then get the task heading above it
106
+ # Try standard format first (checkbox at line start)
107
+ CURRENT_TASK_LINE=$(grep -B1 '^\[ \]' "$TASKS_FILE" 2>/dev/null | grep -E '^##+ T-' | head -1 || echo "")
108
+
109
+ # If not found, try inline format (**Status**: [ ])
110
+ if [[ -z "$CURRENT_TASK_LINE" ]]; then
111
+ # Find line with **Status**: [ ], then look backward for task heading
112
+ TASK_LINE_NUM=$(grep -n '\*\*Status\*\*: \[ \]' "$TASKS_FILE" 2>/dev/null | head -1 | cut -d: -f1 || echo "")
113
+ if [[ -n "$TASK_LINE_NUM" ]]; then
114
+ # Get lines before the status line and find the task heading
115
+ CURRENT_TASK_LINE=$(head -n "$TASK_LINE_NUM" "$TASKS_FILE" | grep -E '^##+ T-' | tail -1 || echo "")
116
+ fi
117
+ fi
92
118
  CURRENT_TASK_ID=""
93
119
  CURRENT_TASK_TITLE=""
94
120
 
@@ -347,16 +347,52 @@ create_github_issue() {
347
347
  }
348
348
  ' "$tasks_file")
349
349
 
350
- # Detect repository from git remote
351
- local repo=$(git remote get-url origin 2>/dev/null | sed 's/.*github\.com[:/]\(.*\)\.git/\1/' | sed 's/.*github\.com[:/]\(.*\)/\1/')
350
+ # Detect repository from profile-based config
351
+ local repo=""
352
+ local owner=""
353
+ local repo_name=""
352
354
 
355
+ # First, check if increment has a specific githubProfile in metadata
356
+ local metadata_file="$2/metadata.json"
357
+ local profile_id=""
358
+
359
+ if [ -f "$metadata_file" ]; then
360
+ profile_id=$(cat "$metadata_file" 2>/dev/null | grep -o '"githubProfile"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
361
+ log_debug "Found githubProfile in metadata: $profile_id"
362
+ fi
363
+
364
+ # If no profile in metadata, use activeProfile from config
365
+ if [ -z "$profile_id" ]; then
366
+ profile_id=$(cat "$CONFIG_FILE" 2>/dev/null | grep -o '"activeProfile"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
367
+ log_debug "Using activeProfile from config: $profile_id"
368
+ fi
369
+
370
+ if [ -n "$profile_id" ]; then
371
+ # Extract owner and repo from the profile
372
+ local profile_section=$(cat "$CONFIG_FILE" 2>/dev/null | awk "/$profile_id/,/^[[:space:]]*\}/{print}")
373
+ owner=$(echo "$profile_section" | grep -o '"owner"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
374
+ repo_name=$(echo "$profile_section" | grep -o '"repo"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
375
+
376
+ if [ -n "$owner" ] && [ -n "$repo_name" ]; then
377
+ repo="$owner/$repo_name"
378
+ log_debug "Using repo from profile: $repo"
379
+ fi
380
+ fi
381
+
382
+ # Fallback to git remote detection if no profile config found
383
+ if [ -z "$repo" ]; then
384
+ repo=$(git remote get-url origin 2>/dev/null | sed 's/.*github\.com[:/]\(.*\)\.git/\1/' | sed 's/.*github\.com[:/]\(.*\)/\1/')
385
+ log_debug "Fallback to git remote: $repo"
386
+ fi
387
+
388
+ # Legacy fallback to old config format
353
389
  if [ -z "$repo" ]; then
354
- # Fallback to config
355
390
  repo=$(cat "$CONFIG_FILE" 2>/dev/null | grep -A 5 '"sync"' | grep -o '"repo"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
391
+ log_debug "Legacy fallback to old config: $repo"
356
392
  fi
357
393
 
358
394
  if [ -z "$repo" ]; then
359
- log_error "Could not detect GitHub repository"
395
+ log_error "Could not detect GitHub repository from profile or git remote"
360
396
  return 1
361
397
  fi
362
398
 
@@ -444,7 +480,15 @@ EOF
444
480
  # Update existing metadata.json with github section
445
481
  if command -v jq >/dev/null 2>&1; then
446
482
  local temp_metadata=$(mktemp)
447
- jq ". + {\"github\": {\"issue\": $issue_number, \"url\": \"$issue_url\", \"synced\": \"$current_timestamp\"}}" "$metadata_file" > "$temp_metadata"
483
+ local jq_update=". + {\"github\": {\"issue\": $issue_number, \"url\": \"$issue_url\", \"synced\": \"$current_timestamp\"}"
484
+
485
+ # Add profile ID if we have it
486
+ if [ -n "$profile_id" ]; then
487
+ jq_update="$jq_update, \"githubProfile\": \"$profile_id\""
488
+ jq_update=". + {\"github\": {\"issue\": $issue_number, \"url\": \"$issue_url\", \"synced\": \"$current_timestamp\"}, \"githubProfile\": \"$profile_id\"}"
489
+ fi
490
+
491
+ jq "$jq_update" "$metadata_file" > "$temp_metadata"
448
492
  mv "$temp_metadata" "$metadata_file"
449
493
  else
450
494
  # Fallback: manual JSON construction (less reliable)
@@ -455,6 +499,7 @@ EOF
455
499
  "status": "active",
456
500
  "type": "feature",
457
501
  "created": "$current_timestamp",
502
+ "githubProfile": "$profile_id",
458
503
  "github": {
459
504
  "issue": $issue_number,
460
505
  "url": "$issue_url",
@@ -20,40 +20,96 @@ PROMPT=$(echo "$INPUT" | node -e "
20
20
  # ==============================================================================
21
21
 
22
22
  if echo "$PROMPT" | grep -q "/specweave:increment"; then
23
- # Check for incomplete increments
23
+ # Check increment discipline using check-discipline CLI command
24
+ # This enforces WIP limits (max 1 active, hard cap 2)
24
25
  SPECWEAVE_DIR=".specweave"
25
26
 
26
27
  if [[ -d "$SPECWEAVE_DIR/increments" ]]; then
27
- INCOMPLETE_INCREMENTS=$(find "$SPECWEAVE_DIR/increments" -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
28
- metadata="$increment_dir/metadata.json"
29
- if [[ -f "$metadata" ]]; then
30
- status=$(node -e "
28
+ # Run discipline check (exit code: 0=pass, 1=violations, 2=error)
29
+ if command -v node >/dev/null 2>&1 && [[ -f "dist/cli/index.js" ]]; then
30
+ # Check active increments using MetadataManager
31
+ ACTIVE_COUNT=$(node -e "
32
+ try {
33
+ const { MetadataManager } = require('./dist/core/increment/metadata-manager.js');
34
+ const active = MetadataManager.getActive();
35
+ console.log(active.length);
36
+ } catch (e) {
37
+ console.error('Error checking active increments:', e.message);
38
+ process.exit(2);
39
+ }
40
+ " 2>/dev/null || echo "0")
41
+
42
+ # Hard cap: never >2 active
43
+ if [[ "$ACTIVE_COUNT" -ge 2 ]]; then
44
+ # Get list of active increments for error message
45
+ ACTIVE_LIST=$(node -e "
31
46
  try {
32
- const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
33
- console.log(data.status || 'unknown');
34
- } catch (e) {
35
- console.log('unknown');
36
- }
37
- ")
47
+ const { MetadataManager } = require('./dist/core/increment/metadata-manager.js');
48
+ const active = MetadataManager.getActive();
49
+ active.forEach(inc => console.log(' - ' + inc.id + ' [' + inc.type + ']'));
50
+ } catch (e) {}
51
+ " 2>/dev/null || echo "")
38
52
 
39
- if [[ "$status" == "active" || "$status" == "planning" ]]; then
40
- echo "$(basename "$increment_dir")"
41
- fi
53
+ cat <<EOF
54
+ {
55
+ "decision": "block",
56
+ "reason": "❌ HARD CAP REACHED\n\nYou have $ACTIVE_COUNT active increments (absolute maximum: 2)\n\nActive increments:\n$ACTIVE_LIST\n\n💡 You MUST complete or pause existing work first:\n\n1️⃣ Complete an increment:\n /specweave:done <id>\n\n2️⃣ Pause an increment:\n /specweave:pause <id> --reason=\"...\"\n\n3️⃣ Check status:\n /specweave:status\n\n📝 Multiple hotfixes? Combine them into ONE increment!\n Example: 0009-security-fixes (SQL + XSS + CSRF)\n\n⛔ This limit is enforced for your productivity.\nResearch: 3+ concurrent tasks = 40% slower + more bugs"
57
+ }
58
+ EOF
59
+ exit 0
42
60
  fi
43
- done)
44
61
 
45
- if [[ -n "$INCOMPLETE_INCREMENTS" ]]; then
46
- # Count incomplete increments
47
- COUNT=$(echo "$INCOMPLETE_INCREMENTS" | wc -l | xargs)
62
+ # Soft warning: 1 active (recommended limit)
63
+ if [[ "$ACTIVE_COUNT" -ge 1 ]]; then
64
+ # Get list of active increments for warning
65
+ ACTIVE_LIST=$(node -e "
66
+ try {
67
+ const { MetadataManager } = require('./dist/core/increment/metadata-manager.js');
68
+ const active = MetadataManager.getActive();
69
+ active.forEach(inc => console.log(' - ' + inc.id + ' [' + inc.type + ']'));
70
+ } catch (e) {}
71
+ " 2>/dev/null || echo "")
72
+
73
+ # Just warn, don't block (user can choose to continue)
74
+ cat <<EOF
75
+ {
76
+ "decision": "approve",
77
+ "systemMessage": "⚠️ WIP LIMIT REACHED\n\nYou have $ACTIVE_COUNT active increment (recommended limit: 1)\n\nActive increments:\n$ACTIVE_LIST\n\n🧠 Focus Principle: ONE active increment = maximum productivity\nStarting a 2nd increment reduces focus and velocity.\n\n💡 Consider:\n 1️⃣ Complete current work (recommended)\n 2️⃣ Pause current work (/specweave:pause)\n 3️⃣ Continue anyway (accept 20% productivity cost)\n\n⚠️ Emergency hotfix/bug? Use --type=hotfix or --type=bug to bypass this warning."
78
+ }
79
+ EOF
80
+ exit 0
81
+ fi
82
+ else
83
+ # Fallback: check for active/planning status manually
84
+ INCOMPLETE_INCREMENTS=$(find "$SPECWEAVE_DIR/increments" -mindepth 1 -maxdepth 1 -type d | while read increment_dir; do
85
+ metadata="$increment_dir/metadata.json"
86
+ if [[ -f "$metadata" ]]; then
87
+ status=$(node -e "
88
+ try {
89
+ const data = JSON.parse(require('fs').readFileSync('$metadata', 'utf-8'));
90
+ console.log(data.status || 'unknown');
91
+ } catch (e) {
92
+ console.log('unknown');
93
+ }
94
+ ")
95
+
96
+ if [[ "$status" == "active" || "$status" == "planning" ]]; then
97
+ echo "$(basename "$increment_dir")"
98
+ fi
99
+ fi
100
+ done)
48
101
 
49
- # Block execution
50
- cat <<EOF
102
+ if [[ -n "$INCOMPLETE_INCREMENTS" ]]; then
103
+ COUNT=$(echo "$INCOMPLETE_INCREMENTS" | wc -l | xargs)
104
+
105
+ cat <<EOF
51
106
  {
52
107
  "decision": "block",
53
108
  "reason": "❌ Cannot create new increment! You have $COUNT incomplete increment(s):\n\n$(echo "$INCOMPLETE_INCREMENTS" | sed 's/^/ - /')\n\n💡 Complete or close them first:\n - /specweave:done <id> # Mark as complete\n - /specweave:pause <id> # Pause for later\n - /specweave:abandon <id> # Abandon if obsolete\n\nℹ️ The discipline exists for a reason:\n ✓ Prevents scope creep\n ✓ Ensures completions are tracked\n ✓ Maintains living docs accuracy\n ✓ Keeps work focused"
54
109
  }
55
110
  EOF
56
- exit 0
111
+ exit 0
112
+ fi
57
113
  fi
58
114
  fi
59
115
  fi