specdrive-cli 0.1.0 → 0.1.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/README.md +24 -4
- package/package.json +2 -2
- package/src/index.js +107 -4
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ This makes AI-generated specs and code safe for enterprise use.
|
|
|
65
65
|
### Global Install
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
npm install -g specdrive@latest
|
|
68
|
+
npm install -g specdrive-cli@latest
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
### Verify Install
|
|
@@ -327,6 +327,10 @@ Audits and manages AI skills.
|
|
|
327
327
|
| `sdrive status` | Show feature lifecycle state |
|
|
328
328
|
| `sdrive approve <gate>` | Approve a workflow gate |
|
|
329
329
|
| `sdrive create <feature>` | Create feature scaffold (internal) |
|
|
330
|
+
| `sdrive team:add <username> <role>` | Add a new team member |
|
|
331
|
+
| `sdrive team:remove <username>` | Remove a team member |
|
|
332
|
+
| `sdrive team:list` | List all team members |
|
|
333
|
+
| `sdrive team:update <username> <role>` | Update a member's role |
|
|
330
334
|
|
|
331
335
|
---
|
|
332
336
|
|
|
@@ -428,13 +432,29 @@ jobs:
|
|
|
428
432
|
| Cursor | `.cursor/rules/sdrive.md` |
|
|
429
433
|
| VS Code | `.vscode/sdrive-commands.json` |
|
|
430
434
|
|
|
431
|
-
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Team Collaboration
|
|
440
|
+
|
|
441
|
+
SpecDrive supports team roles.
|
|
442
|
+
|
|
443
|
+
| Role | Can approve | Can implement |
|
|
444
|
+
|------|-------------|---------------|
|
|
445
|
+
| admin | ✅ | ✅ |
|
|
446
|
+
| reviewer | ✅ | ❌ |
|
|
447
|
+
| developer | ❌ | ✅ |
|
|
448
|
+
|
|
449
|
+
Manage team:
|
|
432
450
|
|
|
433
451
|
```bash
|
|
434
|
-
|
|
452
|
+
sdrive team:add alice admin
|
|
453
|
+
sdrive team:list
|
|
454
|
+
sdrive team:update bob reviewer
|
|
455
|
+
sdrive team:remove carol
|
|
435
456
|
```
|
|
436
457
|
|
|
437
|
-
---
|
|
438
458
|
|
|
439
459
|
## Contributing
|
|
440
460
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -20,7 +20,6 @@ program
|
|
|
20
20
|
const projectRoot = process.cwd();
|
|
21
21
|
const sdriveRoot = path.join(projectRoot, ".sdrive");
|
|
22
22
|
|
|
23
|
-
// Create .sdrive directory structure
|
|
24
23
|
const dirs = [
|
|
25
24
|
path.join(sdriveRoot, "agents"),
|
|
26
25
|
path.join(sdriveRoot, "commands"),
|
|
@@ -44,21 +43,18 @@ program
|
|
|
44
43
|
console.log(` Created: ${path.relative(projectRoot, dir)}`);
|
|
45
44
|
});
|
|
46
45
|
|
|
47
|
-
// Create workflow-state.json if missing
|
|
48
46
|
const workflowStatePath = path.join(sdriveRoot, "workflow-state.json");
|
|
49
47
|
if (!fs.existsSync(workflowStatePath)) {
|
|
50
48
|
fs.writeFileSync(workflowStatePath, JSON.stringify({ features: [] }, null, 2));
|
|
51
49
|
console.log(" Created: .sdrive/workflow-state.json");
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
// Create constitution.md if missing
|
|
55
52
|
const constitutionPath = path.join(sdriveRoot, "constitution.md");
|
|
56
53
|
if (!fs.existsSync(constitutionPath)) {
|
|
57
54
|
fs.writeFileSync(constitutionPath, "# SpecDrive Constitution\n\nInitialized by `sdrive init`.\n");
|
|
58
55
|
console.log(" Created: .sdrive/constitution.md");
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
// Generate tool adapters
|
|
62
58
|
const generatorPath = path.join(sdriveRoot, "scripts", "generate-adapters.js");
|
|
63
59
|
if (fs.existsSync(generatorPath)) {
|
|
64
60
|
try {
|
|
@@ -232,4 +228,111 @@ program
|
|
|
232
228
|
console.log(`✅ Gate "${gate}" approved for feature "${feature.name}".`);
|
|
233
229
|
});
|
|
234
230
|
|
|
231
|
+
// ---------- TEAM COMMANDS ----------
|
|
232
|
+
|
|
233
|
+
program
|
|
234
|
+
.command("team:add <username> <role>")
|
|
235
|
+
.description("Add a new team member")
|
|
236
|
+
.action((username, role) => {
|
|
237
|
+
const teamPath = path.join(process.cwd(), ".sdrive", "team.json");
|
|
238
|
+
|
|
239
|
+
if (!fs.existsSync(teamPath)) {
|
|
240
|
+
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const team = JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
245
|
+
|
|
246
|
+
if (!team.members) {
|
|
247
|
+
team.members = [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const existing = team.members.find((m) => m.username === username);
|
|
251
|
+
if (existing) {
|
|
252
|
+
console.error(`❌ User "${username}" already exists. Use team:update to change role.`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
team.members.push({ username, role });
|
|
257
|
+
fs.writeFileSync(teamPath, JSON.stringify(team, null, 2));
|
|
258
|
+
|
|
259
|
+
console.log(`✅ Added ${username} as ${role}.`);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
program
|
|
263
|
+
.command("team:remove <username>")
|
|
264
|
+
.description("Remove a team member")
|
|
265
|
+
.action((username) => {
|
|
266
|
+
const teamPath = path.join(process.cwd(), ".sdrive", "team.json");
|
|
267
|
+
|
|
268
|
+
if (!fs.existsSync(teamPath)) {
|
|
269
|
+
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const team = JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
274
|
+
|
|
275
|
+
const before = team.members?.length || 0;
|
|
276
|
+
team.members = (team.members || []).filter((m) => m.username !== username);
|
|
277
|
+
const after = team.members.length;
|
|
278
|
+
|
|
279
|
+
if (before === after) {
|
|
280
|
+
console.error(`❌ User "${username}" not found.`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fs.writeFileSync(teamPath, JSON.stringify(team, null, 2));
|
|
285
|
+
console.log(`✅ Removed ${username}.`);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
program
|
|
289
|
+
.command("team:list")
|
|
290
|
+
.description("List all team members")
|
|
291
|
+
.action(() => {
|
|
292
|
+
const teamPath = path.join(process.cwd(), ".sdrive", "team.json");
|
|
293
|
+
|
|
294
|
+
if (!fs.existsSync(teamPath)) {
|
|
295
|
+
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const team = JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
300
|
+
|
|
301
|
+
if (!team.members || team.members.length === 0) {
|
|
302
|
+
console.log("No team members found.");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log("\nSpecDrive Team Members\n");
|
|
307
|
+
team.members.forEach((m) => {
|
|
308
|
+
console.log(`👤 ${m.username} — ${m.role}`);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
program
|
|
313
|
+
.command("team:update <username> <role>")
|
|
314
|
+
.description("Update a team member's role")
|
|
315
|
+
.action((username, role) => {
|
|
316
|
+
const teamPath = path.join(process.cwd(), ".sdrive", "team.json");
|
|
317
|
+
|
|
318
|
+
if (!fs.existsSync(teamPath)) {
|
|
319
|
+
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const team = JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
324
|
+
|
|
325
|
+
const member = team.members?.find((m) => m.username === username);
|
|
326
|
+
|
|
327
|
+
if (!member) {
|
|
328
|
+
console.error(`❌ User "${username}" not found.`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
member.role = role;
|
|
333
|
+
fs.writeFileSync(teamPath, JSON.stringify(team, null, 2));
|
|
334
|
+
|
|
335
|
+
console.log(`✅ Updated ${username} to ${role}.`);
|
|
336
|
+
});
|
|
337
|
+
|
|
235
338
|
program.parse(process.argv);
|