specdrive-cli 0.1.10 → 0.1.12

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 (74) hide show
  1. package/README.md +955 -697
  2. package/agents/00-onboarding.md +261 -0
  3. package/agents/01-constitution.md +214 -201
  4. package/agents/02-specification.md +249 -226
  5. package/agents/03-uiux.md +156 -144
  6. package/agents/04-cascade.md +151 -122
  7. package/agents/05-discover-skills.md +136 -136
  8. package/agents/06-documentation.md +158 -145
  9. package/agents/07-implementation.md +201 -169
  10. package/agents/08-performance.md +179 -165
  11. package/agents/09-review-complete.md +239 -168
  12. package/agents/10-security.md +180 -167
  13. package/agents/11-test.md +195 -0
  14. package/commands/gates.js +73 -73
  15. package/commands/manifest.json +113 -95
  16. package/commands/permissions.json +39 -0
  17. package/commands/router.js +151 -127
  18. package/commands/tools.json +19 -19
  19. package/dashboard/app.js +394 -0
  20. package/dashboard/index.html +74 -0
  21. package/dashboard/server.js +166 -0
  22. package/dashboard/style.css +157 -0
  23. package/mcp/mcp.json +31 -0
  24. package/mcp/server.js +108 -0
  25. package/package.json +35 -32
  26. package/schemas/config.schema.json +20 -0
  27. package/schemas/workflow-state.schema.json +149 -38
  28. package/scripts/anti-redundancy.js +176 -176
  29. package/scripts/audit-log.js +46 -46
  30. package/scripts/check-permission.js +87 -0
  31. package/scripts/diff-spec.js +50 -50
  32. package/scripts/diff-version.js +96 -0
  33. package/scripts/generate-adapters.js +80 -80
  34. package/scripts/generate-from-template.js +97 -97
  35. package/scripts/generate-openapi.js +75 -75
  36. package/scripts/github-team-sync.js +80 -80
  37. package/scripts/install-hooks.js +20 -20
  38. package/scripts/load-plugins.js +65 -65
  39. package/scripts/migrate-openspec.js +318 -0
  40. package/scripts/migrate-speckit.js +322 -0
  41. package/scripts/migrate.js +12 -62
  42. package/scripts/onboard.js +312 -0
  43. package/scripts/pre-commit.js +56 -20
  44. package/scripts/team.js +113 -113
  45. package/scripts/test-adapters.js +118 -118
  46. package/scripts/test-create.js +13 -13
  47. package/scripts/test-end-to-end.js +137 -137
  48. package/scripts/test-router.js +110 -110
  49. package/scripts/test-state-transitions.js +146 -146
  50. package/scripts/test-validator.js +152 -152
  51. package/scripts/validate-config.js +36 -0
  52. package/scripts/validate-governance.js +150 -130
  53. package/scripts/verify.js +525 -0
  54. package/scripts/version-new.js +202 -0
  55. package/src/index.js +1010 -807
  56. package/templates/expo/plan.json +12 -0
  57. package/templates/expo/spec.json +12 -0
  58. package/templates/expo/tasks.json +5 -0
  59. package/templates/fastapi/plan.json +12 -0
  60. package/templates/fastapi/spec.json +12 -0
  61. package/templates/fastapi/tasks.json +5 -0
  62. package/templates/generic/plan.json +12 -0
  63. package/templates/generic/spec.json +11 -0
  64. package/templates/generic/tasks.json +5 -0
  65. package/templates/nextjs/plan.json +23 -0
  66. package/templates/nextjs/spec.json +12 -0
  67. package/templates/nextjs/tasks.json +5 -0
  68. package/templates/react-node/plan.json +15 -0
  69. package/templates/react-node/spec.json +12 -0
  70. package/templates/react-node/tasks.json +5 -0
  71. package/templates/registry.json +30 -0
  72. package/templates/turborepo/plan.json +12 -0
  73. package/templates/turborepo/spec.json +12 -0
  74. package/templates/turborepo/tasks.json +5 -0
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectRoot = process.cwd();
7
+ const sdriveRoot = path.join(projectRoot, ".sdrive");
8
+
9
+ const featureName = process.argv[2];
10
+ const versionA = process.argv[3];
11
+ const versionB = process.argv[4];
12
+
13
+ if (!featureName || !versionA || !versionB) {
14
+ console.error("❌ Usage: node diff-version.js <feature> <v1> <v2>");
15
+ process.exit(1);
16
+ }
17
+
18
+ function findFile(baseDir, version, fileName) {
19
+ const candidate = path.join(baseDir, featureName, version, fileName);
20
+
21
+ if (fs.existsSync(candidate)) {
22
+ return candidate;
23
+ }
24
+
25
+ return null;
26
+ }
27
+
28
+ function readLines(filePath) {
29
+ return fs.readFileSync(filePath, "utf8").split("\n");
30
+ }
31
+
32
+ function simpleDiff(oldLines, newLines) {
33
+ const added = [];
34
+ const removed = [];
35
+ const unchanged = [];
36
+
37
+ const oldSet = new Set(oldLines);
38
+ const newSet = new Set(newLines);
39
+
40
+ newLines.forEach((line) => {
41
+ if (oldSet.has(line)) {
42
+ unchanged.push(line);
43
+ } else {
44
+ added.push(line);
45
+ }
46
+ });
47
+
48
+ oldLines.forEach((line) => {
49
+ if (!newSet.has(line)) {
50
+ removed.push(line);
51
+ }
52
+ });
53
+
54
+ return { added, removed, unchanged };
55
+ }
56
+
57
+ function diffFile(fileName, label) {
58
+ const specA = findFile(path.join(sdriveRoot, "specs", "ongoing"), versionA, fileName);
59
+ const specB = findFile(path.join(sdriveRoot, "specs", "ongoing"), versionB, fileName);
60
+
61
+ if (!specA || !specB) {
62
+ console.log(`\n⚠️ ${label}: not both versions have this file. Skipping.`);
63
+ return;
64
+ }
65
+
66
+ const oldLines = readLines(specA);
67
+ const newLines = readLines(specB);
68
+ const { added, removed } = simpleDiff(oldLines, newLines);
69
+
70
+ console.log(`\n📄 ${label} (${versionA} → ${versionB})`);
71
+
72
+ if (added.length === 0 && removed.length === 0) {
73
+ console.log(" ✅ No changes.");
74
+ return;
75
+ }
76
+
77
+ if (removed.length > 0) {
78
+ console.log(" ❌ Removed:");
79
+ removed.forEach((line) => console.log(` - ${line}`));
80
+ }
81
+
82
+ if (added.length > 0) {
83
+ console.log(" ✅ Added:");
84
+ added.forEach((line) => console.log(` + ${line}`));
85
+ }
86
+ }
87
+
88
+ function main() {
89
+ console.log(`\n🔍 Diff: ${featureName} (${versionA} → ${versionB})`);
90
+
91
+ diffFile("spec.md", "Specification");
92
+ diffFile("plan.md", "Plan");
93
+ diffFile("tasks.md", "Tasks");
94
+ }
95
+
96
+ main();
@@ -1,81 +1,81 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- const projectRoot = process.cwd();
5
-
6
- function loadTools() {
7
- const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
8
-
9
- if (!fs.existsSync(toolsPath)) {
10
- console.error("❌ tools.json not found.");
11
- process.exit(1);
12
- }
13
-
14
- return JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
15
- }
16
-
17
- function adapterContent(toolName) {
18
- return `# SpecDrive Commands
19
-
20
- This project uses SpecDrive for spec-driven development.
21
-
22
- ## How to use
23
-
24
- When the user types a /sdrive:* command, follow these steps:
25
-
26
- 1. Read .sdrive/commands/manifest.json
27
- 2. Find the matching command
28
- 3. Read the agent file listed in agentFile
29
- 4. Follow that agent's instructions exactly
30
- 5. Run validators after writing files
31
- 6. Stop at human approval gates
32
-
33
- Always read the manifest first. Never hardcode the command list.
34
- `;
35
- }
36
-
37
- function generateForTools(tools) {
38
- tools.forEach((tool) => {
39
- const adapterFullPath = path.join(projectRoot, tool.adapterPath);
40
- const adapterDir = path.dirname(adapterFullPath);
41
-
42
- if (!fs.existsSync(adapterDir)) {
43
- fs.mkdirSync(adapterDir, { recursive: true });
44
- }
45
-
46
- if (fs.existsSync(adapterFullPath)) {
47
- console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
48
- return;
49
- }
50
-
51
- fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
52
- console.log(`✅ Generated: ${tool.adapterPath}`);
53
- });
54
- }
55
-
56
- function generateAll() {
57
- const tools = loadTools();
58
- generateForTools(tools);
59
- console.log("\n🎉 All adapters generated successfully.");
60
- }
61
-
62
- function generateSelected(indexes) {
63
- const tools = loadTools();
64
- const selectedTools = indexes
65
- .map((i) => tools[i])
66
- .filter((tool) => tool);
67
-
68
- if (selectedTools.length === 0) {
69
- console.error("❌ No valid tools selected.");
70
- process.exit(1);
71
- }
72
-
73
- generateForTools(selectedTools);
74
- console.log("\n🎉 Selected adapters generated successfully.");
75
- }
76
-
77
- module.exports = {
78
- generateAll,
79
- generateSelected,
80
- loadTools
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const projectRoot = process.cwd();
5
+
6
+ function loadTools() {
7
+ const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
8
+
9
+ if (!fs.existsSync(toolsPath)) {
10
+ console.error("❌ tools.json not found.");
11
+ process.exit(1);
12
+ }
13
+
14
+ return JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
15
+ }
16
+
17
+ function adapterContent(toolName) {
18
+ return `# SpecDrive Commands
19
+
20
+ This project uses SpecDrive for spec-driven development.
21
+
22
+ ## How to use
23
+
24
+ When the user types a /sdrive:* command, follow these steps:
25
+
26
+ 1. Read .sdrive/commands/manifest.json
27
+ 2. Find the matching command
28
+ 3. Read the agent file listed in agentFile
29
+ 4. Follow that agent's instructions exactly
30
+ 5. Run validators after writing files
31
+ 6. Stop at human approval gates
32
+
33
+ Always read the manifest first. Never hardcode the command list.
34
+ `;
35
+ }
36
+
37
+ function generateForTools(tools) {
38
+ tools.forEach((tool) => {
39
+ const adapterFullPath = path.join(projectRoot, tool.adapterPath);
40
+ const adapterDir = path.dirname(adapterFullPath);
41
+
42
+ if (!fs.existsSync(adapterDir)) {
43
+ fs.mkdirSync(adapterDir, { recursive: true });
44
+ }
45
+
46
+ if (fs.existsSync(adapterFullPath)) {
47
+ console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
48
+ return;
49
+ }
50
+
51
+ fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
52
+ console.log(`✅ Generated: ${tool.adapterPath}`);
53
+ });
54
+ }
55
+
56
+ function generateAll() {
57
+ const tools = loadTools();
58
+ generateForTools(tools);
59
+ console.log("\n🎉 All adapters generated successfully.");
60
+ }
61
+
62
+ function generateSelected(indexes) {
63
+ const tools = loadTools();
64
+ const selectedTools = indexes
65
+ .map((i) => tools[i])
66
+ .filter((tool) => tool);
67
+
68
+ if (selectedTools.length === 0) {
69
+ console.error("❌ No valid tools selected.");
70
+ process.exit(1);
71
+ }
72
+
73
+ generateForTools(selectedTools);
74
+ console.log("\n🎉 Selected adapters generated successfully.");
75
+ }
76
+
77
+ module.exports = {
78
+ generateAll,
79
+ generateSelected,
80
+ loadTools
81
81
  };
@@ -1,98 +1,98 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
-
4
- const projectRoot = process.cwd();
5
-
6
- function loadRegistry() {
7
- const registryPath = path.join(projectRoot, ".sdrive", "templates", "registry.json");
8
-
9
- if (!fs.existsSync(registryPath)) {
10
- console.error("❌ registry.json not found.");
11
- process.exit(1);
12
- }
13
-
14
- return JSON.parse(fs.readFileSync(registryPath, "utf8")).templates;
15
- }
16
-
17
- function templateExists(templateName) {
18
- const registry = loadRegistry();
19
- return registry.some((t) => t.name === templateName || t.path === templateName);
20
- }
21
-
22
- function listTemplates() {
23
- const registry = loadRegistry();
24
- console.log("\nAvailable SpecDrive Templates\n");
25
- registry.forEach((t) => {
26
- console.log(` ${t.name} — ${t.description}`);
27
- });
28
- }
29
-
30
- function loadTemplate(templateName, fileName) {
31
- const templatePath = path.join(projectRoot, ".sdrive", "templates", templateName, fileName);
32
- return fs.readFileSync(templatePath, "utf8");
33
- }
34
-
35
- function applyVariables(content, variables) {
36
- let result = content;
37
- Object.keys(variables).forEach((key) => {
38
- result = result.replaceAll(`{{${key}}}`, variables[key]);
39
- });
40
- return result;
41
- }
42
-
43
- function generate(templateName, featureName, variables) {
44
- const templateDir = path.join(projectRoot, ".sdrive", "templates", templateName);
45
- const govDir = path.join(projectRoot, ".sdrive", "governance", featureName);
46
-
47
- if (!fs.existsSync(templateDir)) {
48
- console.error(`❌ Template "${templateName}" not found.`);
49
- listTemplates();
50
- process.exit(1);
51
- }
52
-
53
- fs.mkdirSync(govDir, { recursive: true });
54
-
55
- const files = ["spec.json", "plan.json", "tasks.json"];
56
-
57
- files.forEach((file) => {
58
- const templateFile = path.join(templateDir, file);
59
-
60
- if (!fs.existsSync(templateFile)) {
61
- return;
62
- }
63
-
64
- let content = loadTemplate(templateName, file);
65
- content = applyVariables(content, {
66
- featureName,
67
- ...variables
68
- });
69
-
70
- fs.writeFileSync(path.join(govDir, file), content);
71
- console.log(`✅ Generated: ${featureName}/${file}`);
72
- });
73
-
74
- console.log(`\n🎉 Feature "${featureName}" generated from "${templateName}" template.`);
75
- }
76
-
77
- const templateName = process.argv[2];
78
- const featureName = process.argv[3];
79
- const problemStatement = process.argv[4] || "";
80
- const proposedSolution = process.argv[5] || "";
81
- const targetAudience = process.argv[6] || "";
82
- const approach = process.argv[7] || "";
83
- const flowDescription = process.argv[8] || "";
84
-
85
- if (!templateName || !featureName) {
86
- console.error("❌ Usage: node generate-from-template.js <template> <feature> [problem] [solution] [audience] [approach] [flow]");
87
- listTemplates();
88
- process.exit(1);
89
- }
90
-
91
- generate(templateName, featureName, {
92
- title: featureName,
93
- problemStatement,
94
- proposedSolution,
95
- targetAudience,
96
- approach,
97
- flowDescription
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const projectRoot = process.cwd();
5
+
6
+ function loadRegistry() {
7
+ const registryPath = path.join(projectRoot, ".sdrive", "templates", "registry.json");
8
+
9
+ if (!fs.existsSync(registryPath)) {
10
+ console.error("❌ registry.json not found.");
11
+ process.exit(1);
12
+ }
13
+
14
+ return JSON.parse(fs.readFileSync(registryPath, "utf8")).templates;
15
+ }
16
+
17
+ function templateExists(templateName) {
18
+ const registry = loadRegistry();
19
+ return registry.some((t) => t.name === templateName || t.path === templateName);
20
+ }
21
+
22
+ function listTemplates() {
23
+ const registry = loadRegistry();
24
+ console.log("\nAvailable SpecDrive Templates\n");
25
+ registry.forEach((t) => {
26
+ console.log(` ${t.name} — ${t.description}`);
27
+ });
28
+ }
29
+
30
+ function loadTemplate(templateName, fileName) {
31
+ const templatePath = path.join(projectRoot, ".sdrive", "templates", templateName, fileName);
32
+ return fs.readFileSync(templatePath, "utf8");
33
+ }
34
+
35
+ function applyVariables(content, variables) {
36
+ let result = content;
37
+ Object.keys(variables).forEach((key) => {
38
+ result = result.replaceAll(`{{${key}}}`, variables[key]);
39
+ });
40
+ return result;
41
+ }
42
+
43
+ function generate(templateName, featureName, variables) {
44
+ const templateDir = path.join(projectRoot, ".sdrive", "templates", templateName);
45
+ const govDir = path.join(projectRoot, ".sdrive", "governance", featureName);
46
+
47
+ if (!fs.existsSync(templateDir)) {
48
+ console.error(`❌ Template "${templateName}" not found.`);
49
+ listTemplates();
50
+ process.exit(1);
51
+ }
52
+
53
+ fs.mkdirSync(govDir, { recursive: true });
54
+
55
+ const files = ["spec.json", "plan.json", "tasks.json"];
56
+
57
+ files.forEach((file) => {
58
+ const templateFile = path.join(templateDir, file);
59
+
60
+ if (!fs.existsSync(templateFile)) {
61
+ return;
62
+ }
63
+
64
+ let content = loadTemplate(templateName, file);
65
+ content = applyVariables(content, {
66
+ featureName,
67
+ ...variables
68
+ });
69
+
70
+ fs.writeFileSync(path.join(govDir, file), content);
71
+ console.log(`✅ Generated: ${featureName}/${file}`);
72
+ });
73
+
74
+ console.log(`\n🎉 Feature "${featureName}" generated from "${templateName}" template.`);
75
+ }
76
+
77
+ const templateName = process.argv[2];
78
+ const featureName = process.argv[3];
79
+ const problemStatement = process.argv[4] || "";
80
+ const proposedSolution = process.argv[5] || "";
81
+ const targetAudience = process.argv[6] || "";
82
+ const approach = process.argv[7] || "";
83
+ const flowDescription = process.argv[8] || "";
84
+
85
+ if (!templateName || !featureName) {
86
+ console.error("❌ Usage: node generate-from-template.js <template> <feature> [problem] [solution] [audience] [approach] [flow]");
87
+ listTemplates();
88
+ process.exit(1);
89
+ }
90
+
91
+ generate(templateName, featureName, {
92
+ title: featureName,
93
+ problemStatement,
94
+ proposedSolution,
95
+ targetAudience,
96
+ approach,
97
+ flowDescription
98
98
  });
@@ -1,76 +1,76 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
-
6
- const projectRoot = process.cwd();
7
-
8
- function loadPlan(featureName) {
9
- const planPath = path.join(projectRoot, ".sdrive", "governance", featureName, "plan.json");
10
-
11
- if (!fs.existsSync(planPath)) {
12
- console.error(`❌ plan.json not found for feature "${featureName}".`);
13
- process.exit(1);
14
- }
15
-
16
- return JSON.parse(fs.readFileSync(planPath, "utf8"));
17
- }
18
-
19
- function generateOpenAPI(featureName) {
20
- const plan = loadPlan(featureName);
21
-
22
- const openapi = {
23
- openapi: "3.0.0",
24
- info: {
25
- title: `${featureName} API`,
26
- version: "1.0.0",
27
- description: `API specification for ${featureName}`
28
- },
29
- paths: {}
30
- };
31
-
32
- const contracts = plan.apiContracts || [];
33
-
34
- contracts.forEach((contract) => {
35
- const method = contract.method.toLowerCase();
36
- const apiPath = contract.path;
37
-
38
- if (!openapi.paths[apiPath]) {
39
- openapi.paths[apiPath] = {};
40
- }
41
-
42
- openapi.paths[apiPath][method] = {
43
- summary: `${contract.method} ${apiPath}`,
44
- security: contract.authenticationRequired ? [{ bearerAuth: [] }] : [],
45
- responses: {
46
- 200: {
47
- description: "Successful response"
48
- }
49
- }
50
- };
51
- });
52
-
53
- openapi.components = {
54
- securitySchemes: {
55
- bearerAuth: {
56
- type: "http",
57
- scheme: "bearer"
58
- }
59
- }
60
- };
61
-
62
- const outputPath = path.join(projectRoot, ".sdrive", "governance", featureName, "openapi.json");
63
- fs.writeFileSync(outputPath, JSON.stringify(openapi, null, 2));
64
-
65
- console.log(`✅ Generated OpenAPI spec.`);
66
- console.log(` File: ${outputPath}`);
67
- }
68
-
69
- const featureName = process.argv[2];
70
-
71
- if (!featureName) {
72
- console.error("❌ Usage: node generate-openapi.js <feature-name>");
73
- process.exit(1);
74
- }
75
-
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectRoot = process.cwd();
7
+
8
+ function loadPlan(featureName) {
9
+ const planPath = path.join(projectRoot, ".sdrive", "governance", featureName, "plan.json");
10
+
11
+ if (!fs.existsSync(planPath)) {
12
+ console.error(`❌ plan.json not found for feature "${featureName}".`);
13
+ process.exit(1);
14
+ }
15
+
16
+ return JSON.parse(fs.readFileSync(planPath, "utf8"));
17
+ }
18
+
19
+ function generateOpenAPI(featureName) {
20
+ const plan = loadPlan(featureName);
21
+
22
+ const openapi = {
23
+ openapi: "3.0.0",
24
+ info: {
25
+ title: `${featureName} API`,
26
+ version: "1.0.0",
27
+ description: `API specification for ${featureName}`
28
+ },
29
+ paths: {}
30
+ };
31
+
32
+ const contracts = plan.apiContracts || [];
33
+
34
+ contracts.forEach((contract) => {
35
+ const method = contract.method.toLowerCase();
36
+ const apiPath = contract.path;
37
+
38
+ if (!openapi.paths[apiPath]) {
39
+ openapi.paths[apiPath] = {};
40
+ }
41
+
42
+ openapi.paths[apiPath][method] = {
43
+ summary: `${contract.method} ${apiPath}`,
44
+ security: contract.authenticationRequired ? [{ bearerAuth: [] }] : [],
45
+ responses: {
46
+ 200: {
47
+ description: "Successful response"
48
+ }
49
+ }
50
+ };
51
+ });
52
+
53
+ openapi.components = {
54
+ securitySchemes: {
55
+ bearerAuth: {
56
+ type: "http",
57
+ scheme: "bearer"
58
+ }
59
+ }
60
+ };
61
+
62
+ const outputPath = path.join(projectRoot, ".sdrive", "governance", featureName, "openapi.json");
63
+ fs.writeFileSync(outputPath, JSON.stringify(openapi, null, 2));
64
+
65
+ console.log(`✅ Generated OpenAPI spec.`);
66
+ console.log(` File: ${outputPath}`);
67
+ }
68
+
69
+ const featureName = process.argv[2];
70
+
71
+ if (!featureName) {
72
+ console.error("❌ Usage: node generate-openapi.js <feature-name>");
73
+ process.exit(1);
74
+ }
75
+
76
76
  generateOpenAPI(featureName);