specdrive-cli 0.1.1 → 0.1.3
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 +33 -3
- package/package.json +1 -1
- package/src/index.js +107 -4
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ It adds:
|
|
|
21
21
|
- [Why SpecDrive](#why-specdrive)
|
|
22
22
|
- [Prerequisites](#prerequisites)
|
|
23
23
|
- [Installation](#installation)
|
|
24
|
+
- [Where to Run Commands](#where-to-run-commands)
|
|
24
25
|
- [Quickstart](#quickstart)
|
|
25
26
|
- [Core Workflow](#core-workflow)
|
|
26
27
|
- [Optional Commands](#optional-commands)
|
|
@@ -85,7 +86,16 @@ sdrive init
|
|
|
85
86
|
This creates the `.sdrive/` folder and tool adapters.
|
|
86
87
|
|
|
87
88
|
---
|
|
89
|
+
## Where to Run Commands
|
|
88
90
|
|
|
91
|
+
| Command Type | Where to Run | Example |
|
|
92
|
+
|--------------|--------------|---------|
|
|
93
|
+
| Terminal command | In your terminal / shell | `sdrive init` |
|
|
94
|
+
| Slash command | In your AI tool's chat | `/sdrive:propose "User login"` |
|
|
95
|
+
|
|
96
|
+
**Rule:** If it starts with `sdrive`, run it in the terminal. If it starts with `/sdrive:`, run it in your AI chat.
|
|
97
|
+
|
|
98
|
+
---
|
|
89
99
|
## Quickstart
|
|
90
100
|
|
|
91
101
|
1. Initialize the project:
|
|
@@ -327,6 +337,10 @@ Audits and manages AI skills.
|
|
|
327
337
|
| `sdrive status` | Show feature lifecycle state |
|
|
328
338
|
| `sdrive approve <gate>` | Approve a workflow gate |
|
|
329
339
|
| `sdrive create <feature>` | Create feature scaffold (internal) |
|
|
340
|
+
| `sdrive team:add <username> <role>` | Add a new team member |
|
|
341
|
+
| `sdrive team:remove <username>` | Remove a team member |
|
|
342
|
+
| `sdrive team:list` | List all team members |
|
|
343
|
+
| `sdrive team:update <username> <role>` | Update a member's role |
|
|
330
344
|
|
|
331
345
|
---
|
|
332
346
|
|
|
@@ -428,13 +442,29 @@ jobs:
|
|
|
428
442
|
| Cursor | `.cursor/rules/sdrive.md` |
|
|
429
443
|
| VS Code | `.vscode/sdrive-commands.json` |
|
|
430
444
|
|
|
431
|
-
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Team Collaboration
|
|
450
|
+
|
|
451
|
+
SpecDrive supports team roles.
|
|
452
|
+
|
|
453
|
+
| Role | Can approve | Can implement |
|
|
454
|
+
|------|-------------|---------------|
|
|
455
|
+
| admin | ✅ | ✅ |
|
|
456
|
+
| reviewer | ✅ | ❌ |
|
|
457
|
+
| developer | ❌ | ✅ |
|
|
458
|
+
|
|
459
|
+
Manage team:
|
|
432
460
|
|
|
433
461
|
```bash
|
|
434
|
-
|
|
462
|
+
sdrive team:add alice admin
|
|
463
|
+
sdrive team:list
|
|
464
|
+
sdrive team:update bob reviewer
|
|
465
|
+
sdrive team:remove carol
|
|
435
466
|
```
|
|
436
467
|
|
|
437
|
-
---
|
|
438
468
|
|
|
439
469
|
## Contributing
|
|
440
470
|
|
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);
|