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.
- package/CLAUDE.md +38 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +162 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/helpers/github/increment-profile-selector.d.ts +47 -0
- package/dist/cli/helpers/github/increment-profile-selector.d.ts.map +1 -0
- package/dist/cli/helpers/github/increment-profile-selector.js +186 -0
- package/dist/cli/helpers/github/increment-profile-selector.js.map +1 -0
- package/dist/cli/helpers/github/profile-manager.d.ts +119 -0
- package/dist/cli/helpers/github/profile-manager.d.ts.map +1 -0
- package/dist/cli/helpers/github/profile-manager.js +311 -0
- package/dist/cli/helpers/github/profile-manager.js.map +1 -0
- package/dist/cli/helpers/issue-tracker/github-multi-repo.d.ts +81 -0
- package/dist/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -0
- package/dist/cli/helpers/issue-tracker/github-multi-repo.js +385 -0
- package/dist/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -0
- package/dist/cli/helpers/issue-tracker/github.d.ts +13 -0
- package/dist/cli/helpers/issue-tracker/github.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/github.js +38 -143
- package/dist/cli/helpers/issue-tracker/github.js.map +1 -1
- package/dist/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/index.js +126 -43
- package/dist/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/cli/helpers/issue-tracker/utils.d.ts +8 -0
- package/dist/cli/helpers/issue-tracker/utils.d.ts.map +1 -1
- package/dist/cli/helpers/issue-tracker/utils.js +46 -0
- package/dist/cli/helpers/issue-tracker/utils.js.map +1 -1
- package/dist/core/increment/active-increment-manager.d.ts +79 -0
- package/dist/core/increment/active-increment-manager.d.ts.map +1 -0
- package/dist/core/increment/active-increment-manager.js +153 -0
- package/dist/core/increment/active-increment-manager.js.map +1 -0
- package/dist/core/increment/metadata-manager.d.ts +2 -0
- package/dist/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/core/increment/metadata-manager.js +15 -0
- package/dist/core/increment/metadata-manager.js.map +1 -1
- package/dist/utils/git-detector.d.ts +84 -0
- package/dist/utils/git-detector.d.ts.map +1 -0
- package/dist/utils/git-detector.js +233 -0
- package/dist/utils/git-detector.js.map +1 -0
- package/package.json +2 -2
- package/plugins/specweave/commands/specweave-done.md +109 -1
- package/plugins/specweave/hooks/lib/update-status-line.sh +30 -4
- package/plugins/specweave/hooks/post-increment-planning.sh +50 -5
- package/plugins/specweave/hooks/user-prompt-submit.sh +77 -21
- package/plugins/specweave/skills/increment-planner/SKILL.md +12 -5
- package/plugins/specweave/skills/increment-planner/scripts/feature-utils.js +26 -5
- package/plugins/specweave-ado/skills/ado-sync/SKILL.md +2 -2
- package/plugins/specweave-figma/ARCHITECTURE.md +1 -1
- package/plugins/specweave-figma/README.md +1 -1
- package/plugins/specweave-ml/README.md +1 -1
- package/src/templates/CLAUDE.md.template +8 -9
- 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.
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
|
351
|
-
local repo
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
111
|
+
exit 0
|
|
112
|
+
fi
|
|
57
113
|
fi
|
|
58
114
|
fi
|
|
59
115
|
fi
|