specweave 0.15.1 → 0.16.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.
- 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/user-prompt-submit.sh +77 -21
- 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/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":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAsB7B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,MAAM,GAAuB;QACjC,GAAG;QACH,QAAQ,EAAE,SAAS;KACpB,CAAC;IAEF,kCAAkC;IAClC,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAE3C,kBAAkB;IAClB,MAAM,cAAc,GAAG;QACrB,uCAAuC;QACvC,8CAA8C;QAC9C,iCAAiC;QACjC,sCAAsC;QACtC,qDAAqD;QACrD,+CAA+C;QAC/C,2DAA2D;QAC3D,iDAAiD;QACjD,2DAA2D;QAC3D,wCAAwC;KACzC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC3B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,kBAAkB;IAClB,MAAM,cAAc,GAAG;QACrB,uCAAuC;QACvC,8CAA8C;QAC9C,iCAAiC;QACjC,sCAAsC;QACtC,qBAAqB;QACrB,iDAAiD;QACjD,wCAAwC;KACzC,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC3B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,qBAAqB;IACrB,MAAM,iBAAiB,GAAG;QACxB,0CAA0C;QAC1C,iDAAiD;QACjD,oCAAoC;QACpC,yCAAyC;KAC1C,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,QAAQ,GAAG,WAAW,CAAC;YAC9B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,wBAAwB;IACxB,MAAM,aAAa,GAAG;QACpB,qDAAqD;QACrD,gEAAgE;QAChE,iDAAiD;QACjD,2DAA2D;QAC3D,8BAA8B;QAC9B,kEAAkE;KACnE,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC;YAC1B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACxB,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAAmB;IACxD,iCAAiC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,sBAAsB;SAC9B,CAAC;IACJ,CAAC;IAED,uCAAuC;IACvC,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE;QAC1D,GAAG,EAAE,WAAW;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,2BAA2B;SACpD,CAAC;IACJ,CAAC;IAED,mBAAmB;IACnB,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAEhD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,4DAA4D;QAC5D,uDAAuD;QACvD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAC/D,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;YAElC,mDAAmD;YACnD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBACpC,MAAM,MAAM,GAAc;oBACxB,IAAI;oBACJ,GAAG;oBACH,QAAQ,EAAE,UAAU,CAAC,QAAQ,IAAI,SAAS;oBAC1C,KAAK,EAAE,UAAU,CAAC,KAAK;oBACvB,IAAI,EAAE,UAAU,CAAC,IAAI;iBACtB,CAAC;gBACF,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,EAAE,IAAI;KACb,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,WAAmB;IAC3D,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,WAAmB;IACjE,MAAM,aAAa,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAE7D,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,+BAA+B;IAC/B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAC5D,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,2CAA2C;IAC3C,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,WAAmB;IAChE,MAAM,aAAa,GAAG,MAAM,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7D,OAAO,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChC,OAAO,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,QAAQ,GAAG,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,QAAQ,GAAG,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAoB;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAA2C,EAAE,CAAC;IAE1D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
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
|
|
|
@@ -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
|
|
@@ -47,7 +47,7 @@ description: Bidirectional synchronization between SpecWeave increments and Azur
|
|
|
47
47
|
/plugin list --installed | grep specweave-ado
|
|
48
48
|
|
|
49
49
|
# Install if needed
|
|
50
|
-
/plugin install specweave-ado
|
|
50
|
+
/plugin install specweave-ado
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
### 2. Azure DevOps Personal Access Token (PAT)
|
|
@@ -403,4 +403,4 @@ Sync Enabled: ✅
|
|
|
403
403
|
|
|
404
404
|
**Status**: Ready to use
|
|
405
405
|
**Version**: 0.1.0
|
|
406
|
-
**Plugin**: specweave-ado
|
|
406
|
+
**Plugin**: specweave-ado
|
|
@@ -102,7 +102,7 @@ specweave plugin list
|
|
|
102
102
|
/plugin marketplace add https://raw.githubusercontent.com/anton-abyzov/specweave/main/marketplace/.claude-plugin/marketplace.json
|
|
103
103
|
|
|
104
104
|
# Install figma plugin
|
|
105
|
-
/plugin install specweave-figma
|
|
105
|
+
/plugin install specweave-figma
|
|
106
106
|
|
|
107
107
|
# Verify
|
|
108
108
|
/plugin list
|