specdrive-cli 0.1.0

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.
@@ -0,0 +1,128 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ const projectRoot = process.cwd();
6
+ const generatorPath = path.join(projectRoot, ".sdrive", "scripts", "generate-adapters.js");
7
+
8
+ function testGeneratorCreatesAdapters() {
9
+ console.log("\n🧪 Test 1: Generator creates all adapters");
10
+
11
+ try {
12
+ execSync(`node "${generatorPath}"`, { stdio: "pipe" });
13
+ } catch (error) {
14
+ console.log("āŒ FAIL: Generator failed.");
15
+ return false;
16
+ }
17
+
18
+ const expectedAdapters = [
19
+ ".github/copilot-instructions.md",
20
+ ".clinerules/sdrive.md",
21
+ ".claude/commands/sdrive.md",
22
+ ".cursor/rules/sdrive.md",
23
+ ".vscode/sdrive-commands.json"
24
+ ];
25
+
26
+ for (const adapter of expectedAdapters) {
27
+ const fullPath = path.join(projectRoot, adapter);
28
+ if (fs.existsSync(fullPath)) {
29
+ console.log(` āœ… Found: ${adapter}`);
30
+ } else {
31
+ console.log(` āŒ Missing: ${adapter}`);
32
+ return false;
33
+ }
34
+ }
35
+
36
+ console.log("āœ… PASS: All adapters created.");
37
+ return true;
38
+ }
39
+
40
+ function testAdapterContent() {
41
+ console.log("\n🧪 Test 2: Adapter content includes SpecDrive instructions");
42
+
43
+ const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
44
+
45
+ if (!fs.existsSync(adapterPath)) {
46
+ console.log("āŒ FAIL: Copilot adapter missing.");
47
+ return false;
48
+ }
49
+
50
+ const content = fs.readFileSync(adapterPath, "utf8");
51
+
52
+ if (content.includes(".sdrive/commands/manifest.json")) {
53
+ console.log("āœ… PASS: Adapter contains manifest reference.");
54
+ return true;
55
+ }
56
+
57
+ console.log("āŒ FAIL: Adapter missing manifest reference.");
58
+ return false;
59
+ }
60
+
61
+ function testSafetyRule() {
62
+ console.log("\n🧪 Test 3: Generator does not overwrite existing files");
63
+
64
+ const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
65
+
66
+ // Modify the file
67
+ fs.writeFileSync(adapterPath, "# Custom existing file\n", "utf8");
68
+
69
+ // Run generator
70
+ try {
71
+ execSync(`node "${generatorPath}"`, { stdio: "pipe" });
72
+ } catch (error) {
73
+ console.log("āŒ FAIL: Generator failed.");
74
+ return false;
75
+ }
76
+
77
+ // Check content was not overwritten
78
+ const content = fs.readFileSync(adapterPath, "utf8");
79
+
80
+ if (content === "# Custom existing file\n") {
81
+ console.log("āœ… PASS: Existing file was not overwritten.");
82
+ return true;
83
+ }
84
+
85
+ console.log("āŒ FAIL: Existing file was overwritten.");
86
+ return false;
87
+ }
88
+
89
+ function cleanup() {
90
+ const adapters = [
91
+ ".github/copilot-instructions.md",
92
+ ".clinerules/sdrive.md",
93
+ ".claude/commands/sdrive.md",
94
+ ".cursor/rules/sdrive.md",
95
+ ".vscode/sdrive-commands.json"
96
+ ];
97
+
98
+ adapters.forEach((adapter) => {
99
+ const fullPath = path.join(projectRoot, adapter);
100
+ if (fs.existsSync(fullPath)) {
101
+ fs.rmSync(fullPath, { force: true });
102
+ }
103
+ });
104
+
105
+ console.log("\n🧹 Cleaned up adapters.");
106
+ }
107
+
108
+ function main() {
109
+ console.log("=== SpecDrive Adapter Tests ===\n");
110
+
111
+ let allPassed = true;
112
+
113
+ allPassed &= testGeneratorCreatesAdapters();
114
+ allPassed &= testAdapterContent();
115
+ allPassed &= testSafetyRule();
116
+
117
+ cleanup();
118
+
119
+ if (allPassed) {
120
+ console.log("\nāœ… All adapter tests passed.");
121
+ process.exit(0);
122
+ } else {
123
+ console.log("\nāŒ Some adapter tests failed.");
124
+ process.exit(1);
125
+ }
126
+ }
127
+
128
+ main();
@@ -0,0 +1,14 @@
1
+ const { execSync } = require("child_process");
2
+ const path = require("path");
3
+
4
+ const projectRoot = process.cwd();
5
+
6
+ try {
7
+ execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} create test-feature`, {
8
+ stdio: "inherit"
9
+ });
10
+ process.exit(0);
11
+ } catch (error) {
12
+ console.error("Feature creation failed.");
13
+ process.exit(1);
14
+ }
@@ -0,0 +1,141 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ const projectRoot = process.cwd();
6
+
7
+ function runCommand(command) {
8
+ try {
9
+ execSync(command, { stdio: "pipe" });
10
+ return true;
11
+ } catch (error) {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function resetEnvironment() {
17
+ console.log("🧹 Resetting test environment...");
18
+
19
+ const specDir = path.join(projectRoot, ".sdrive", "specs", "backlog", "test-feature");
20
+ const govDir = path.join(projectRoot, ".sdrive", "governance", "test-feature");
21
+
22
+ if (fs.existsSync(specDir)) {
23
+ fs.rmSync(specDir, { recursive: true, force: true });
24
+ }
25
+ if (fs.existsSync(govDir)) {
26
+ fs.rmSync(govDir, { recursive: true, force: true });
27
+ }
28
+
29
+ const workflowStatePath = path.join(projectRoot, ".sdrive", "workflow-state.json");
30
+ fs.writeFileSync(
31
+ workflowStatePath,
32
+ JSON.stringify({ features: [] }, null, 2)
33
+ );
34
+ }
35
+
36
+ function testFeatureCreation() {
37
+ console.log("\n🧪 Test 1: Feature creation");
38
+
39
+ const passed = runCommand(`node .sdrive/scripts/test-create.js`);
40
+
41
+ if (passed) {
42
+ console.log("āœ… PASS: Feature created.");
43
+ return true;
44
+ }
45
+
46
+ console.log("āŒ FAIL: Feature creation failed.");
47
+ return false;
48
+ }
49
+
50
+ function testSpecGeneration() {
51
+ console.log("\n🧪 Test 2: Spec generation");
52
+
53
+ const specPath = path.join(projectRoot, ".sdrive", "specs", "backlog", "test-feature", "spec.md");
54
+ const govPath = path.join(projectRoot, ".sdrive", "governance", "test-feature", "spec.json");
55
+
56
+ const specExists = fs.existsSync(specPath);
57
+ const govExists = fs.existsSync(govPath);
58
+
59
+ if (specExists && govExists) {
60
+ console.log("āœ… PASS: Spec and governance files exist.");
61
+ return true;
62
+ }
63
+
64
+ console.log("āŒ FAIL: Spec or governance files missing.");
65
+ return false;
66
+ }
67
+
68
+ function testTraceabilityGenerated() {
69
+ console.log("\n🧪 Test 3: Traceability matrix generated");
70
+
71
+ const tracePath = path.join(projectRoot, ".sdrive", "governance", "test-feature", "traceability.json");
72
+
73
+ if (fs.existsSync(tracePath)) {
74
+ console.log("āœ… PASS: Traceability matrix exists.");
75
+ return true;
76
+ }
77
+
78
+ console.log("āŒ FAIL: Traceability matrix missing.");
79
+ return false;
80
+ }
81
+
82
+ function testValidationPasses() {
83
+ console.log("\n🧪 Test 4: Governance validation passes");
84
+
85
+ const passed = runCommand("node .sdrive/scripts/validate-governance.js");
86
+
87
+ if (passed) {
88
+ console.log("āœ… PASS: Governance validation passed.");
89
+ return true;
90
+ }
91
+
92
+ console.log("āŒ FAIL: Governance validation failed.");
93
+ return false;
94
+ }
95
+
96
+ function testWorkflowState() {
97
+ console.log("\n🧪 Test 5: Workflow state updated");
98
+
99
+ const workflowStatePath = path.join(projectRoot, ".sdrive", "workflow-state.json");
100
+
101
+ if (!fs.existsSync(workflowStatePath)) {
102
+ console.log("āŒ FAIL: workflow-state.json missing.");
103
+ return false;
104
+ }
105
+
106
+ const state = JSON.parse(fs.readFileSync(workflowStatePath, "utf8"));
107
+
108
+ if (state.features && Array.isArray(state.features)) {
109
+ console.log("āœ… PASS: Workflow state has features array.");
110
+ return true;
111
+ }
112
+
113
+ console.log("āŒ FAIL: Workflow state invalid.");
114
+ return false;
115
+ }
116
+
117
+ function main() {
118
+ console.log("=== SpecDrive End-to-End Tests ===\n");
119
+
120
+ resetEnvironment();
121
+
122
+ let allPassed = true;
123
+
124
+ allPassed &= testFeatureCreation();
125
+ allPassed &= testSpecGeneration();
126
+ allPassed &= testTraceabilityGenerated();
127
+ allPassed &= testValidationPasses();
128
+ allPassed &= testWorkflowState();
129
+
130
+ resetEnvironment();
131
+
132
+ if (allPassed) {
133
+ console.log("\nāœ… All end-to-end tests passed.");
134
+ process.exit(0);
135
+ } else {
136
+ console.log("\nāŒ Some end-to-end tests failed.");
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ main();
@@ -0,0 +1,105 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ const projectRoot = process.cwd();
6
+ const routerPath = path.join(projectRoot, ".sdrive", "commands", "router.js");
7
+
8
+ function runRouter(command, feature) {
9
+ try {
10
+ const args = feature ? ` ${feature}` : "";
11
+ execSync(`node "${routerPath}" ${command}${args}`, { stdio: "pipe" });
12
+ return true;
13
+ } catch (error) {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ function testKnownCommand() {
19
+ console.log("\n🧪 Test 1: Known command should pass");
20
+ const passed = runRouter("/sdrive:constitution");
21
+
22
+ if (passed) {
23
+ console.log("āœ… PASS: Router accepted /sdrive:constitution.");
24
+ return true;
25
+ }
26
+
27
+ console.log("āŒ FAIL: Router rejected /sdrive:constitution.");
28
+ return false;
29
+ }
30
+
31
+ function testUnknownCommand() {
32
+ console.log("\n🧪 Test 2: Unknown command should fail");
33
+ const passed = runRouter("/sdrive:unknown");
34
+
35
+ if (!passed) {
36
+ console.log("āœ… PASS: Router rejected unknown command.");
37
+ return true;
38
+ }
39
+
40
+ console.log("āŒ FAIL: Router accepted unknown command.");
41
+ return false;
42
+ }
43
+
44
+ function testMissingFeatureForGatedCommand() {
45
+ console.log("\n🧪 Test 3: Gated command without feature should fail");
46
+
47
+ // /sdrive:propose has gates
48
+ const passed = runRouter("/sdrive:propose");
49
+
50
+ if (!passed) {
51
+ console.log("āœ… PASS: Router blocked gated command without feature.");
52
+ return true;
53
+ }
54
+
55
+ console.log("āŒ FAIL: Router allowed gated command without feature.");
56
+ return false;
57
+ }
58
+
59
+ function testFeatureConversion() {
60
+ console.log("\n🧪 Test 4: Natural feature description should convert to kebab-case");
61
+
62
+ // Test the function indirectly by checking output
63
+ const { execSync } = require("child_process");
64
+ try {
65
+ const output = execSync(
66
+ `node "${routerPath}" /sdrive:propose "User login with email"`,
67
+ { stdio: "pipe", encoding: "utf8" }
68
+ );
69
+ if (output.includes("user-login-with-email")) {
70
+ console.log("āœ… PASS: Feature converted to kebab-case.");
71
+ return true;
72
+ }
73
+ console.log("āŒ FAIL: Feature not converted correctly.");
74
+ return false;
75
+ } catch (error) {
76
+ // Command may fail due to gate, but conversion should still show
77
+ if (error.stdout && error.stdout.includes("user-login-with-email")) {
78
+ console.log("āœ… PASS: Feature converted to kebab-case.");
79
+ return true;
80
+ }
81
+ console.log("āŒ FAIL: Feature not converted correctly.");
82
+ return false;
83
+ }
84
+ }
85
+
86
+ function main() {
87
+ console.log("=== SpecDrive Router Tests ===\n");
88
+
89
+ let allPassed = true;
90
+
91
+ allPassed &= testKnownCommand();
92
+ allPassed &= testUnknownCommand();
93
+ allPassed &= testMissingFeatureForGatedCommand();
94
+ allPassed &= testFeatureConversion();
95
+
96
+ if (allPassed) {
97
+ console.log("\nāœ… All router tests passed.");
98
+ process.exit(0);
99
+ } else {
100
+ console.log("\nāŒ Some router tests failed.");
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ main();
@@ -0,0 +1,148 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ const projectRoot = process.cwd();
6
+ const workflowStatePath = path.join(projectRoot, ".sdrive", "workflow-state.json");
7
+ const gatesPath = path.join(projectRoot, ".sdrive", "commands", "gates.js");
8
+
9
+ function resetState() {
10
+ const initialState = {
11
+ features: [
12
+ {
13
+ name: "test-feature",
14
+ phase: "backlog",
15
+ currentStep: "specification",
16
+ gates: {
17
+ specification: "pending",
18
+ planning: "pending",
19
+ implementation: "pending",
20
+ review: "pending"
21
+ },
22
+ history: [
23
+ {
24
+ event: "feature_created",
25
+ timestamp: new Date().toISOString()
26
+ }
27
+ ]
28
+ }
29
+ ]
30
+ };
31
+
32
+ fs.writeFileSync(workflowStatePath, JSON.stringify(initialState, null, 2));
33
+ }
34
+
35
+ function readState() {
36
+ return JSON.parse(fs.readFileSync(workflowStatePath, "utf8"));
37
+ }
38
+
39
+ function approveGate(gate) {
40
+ try {
41
+ execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} approve ${gate}`, {
42
+ stdio: "pipe"
43
+ });
44
+ return true;
45
+ } catch (error) {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ function testInitialState() {
51
+ console.log("\n🧪 Test 1: Initial state should be pending");
52
+ resetState();
53
+ const state = readState();
54
+ const feature = state.features[0];
55
+
56
+ if (
57
+ feature.gates.specification === "pending" &&
58
+ feature.gates.planning === "pending" &&
59
+ feature.gates.implementation === "pending" &&
60
+ feature.gates.review === "pending"
61
+ ) {
62
+ console.log("āœ… PASS: All gates are pending initially.");
63
+ return true;
64
+ }
65
+
66
+ console.log("āŒ FAIL: Initial gates are not pending.");
67
+ return false;
68
+ }
69
+
70
+ function testGateApproval() {
71
+ console.log("\n🧪 Test 2: Gate approval should update state");
72
+ resetState();
73
+
74
+ const passed = approveGate("specification");
75
+
76
+ if (!passed) {
77
+ console.log("āŒ FAIL: Could not approve gate.");
78
+ return false;
79
+ }
80
+
81
+ const state = readState();
82
+ const feature = state.features[0];
83
+
84
+ if (feature.gates.specification === "approved") {
85
+ console.log("āœ… PASS: specification gate approved.");
86
+ return true;
87
+ }
88
+
89
+ console.log("āŒ FAIL: specification gate not approved.");
90
+ return false;
91
+ }
92
+
93
+ function testInvalidGate() {
94
+ console.log("\n🧪 Test 3: Invalid gate should be rejected");
95
+ resetState();
96
+
97
+ try {
98
+ execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} approve invalid-gate`, {
99
+ stdio: "pipe"
100
+ });
101
+ console.log("āŒ FAIL: Invalid gate was accepted.");
102
+ return false;
103
+ } catch (error) {
104
+ console.log("āœ… PASS: Invalid gate was rejected.");
105
+ return true;
106
+ }
107
+ }
108
+
109
+ function testHistory() {
110
+ console.log("\n🧪 Test 4: History should record approvals");
111
+ resetState();
112
+
113
+ approveGate("specification");
114
+
115
+ const state = readState();
116
+ const feature = state.features[0];
117
+
118
+ if (feature.history.some((event) => event.event === "specification_approved")) {
119
+ console.log("āœ… PASS: History recorded approval.");
120
+ return true;
121
+ }
122
+
123
+ console.log("āŒ FAIL: History did not record approval.");
124
+ return false;
125
+ }
126
+
127
+ function main() {
128
+ console.log("=== SpecDrive State Transition Tests ===\n");
129
+
130
+ let allPassed = true;
131
+
132
+ allPassed &= testInitialState();
133
+ allPassed &= testGateApproval();
134
+ allPassed &= testInvalidGate();
135
+ allPassed &= testHistory();
136
+
137
+ resetState();
138
+
139
+ if (allPassed) {
140
+ console.log("\nāœ… All state transition tests passed.");
141
+ process.exit(0);
142
+ } else {
143
+ console.log("\nāŒ Some state transition tests failed.");
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ main();
@@ -0,0 +1,148 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ const projectRoot = process.cwd();
6
+ const govDir = path.join(projectRoot, ".sdrive", "governance", "test-feature");
7
+ const validatorPath = path.join(projectRoot, ".sdrive", "scripts", "validate-governance.js");
8
+
9
+ function createValidFiles() {
10
+ fs.mkdirSync(govDir, { recursive: true });
11
+
12
+ fs.writeFileSync(
13
+ path.join(govDir, "spec.json"),
14
+ JSON.stringify(
15
+ {
16
+ feature: "test-feature",
17
+ version: 1,
18
+ overview: {
19
+ problemStatement: "Test problem",
20
+ proposedSolution: "Test solution",
21
+ targetAudience: "Test users"
22
+ },
23
+ userStories: [
24
+ {
25
+ id: "US-001",
26
+ title: "Test story",
27
+ acceptanceCriteria: [
28
+ {
29
+ id: "AC-001",
30
+ text: "Given test, when test, then test"
31
+ }
32
+ ]
33
+ }
34
+ ],
35
+ functionalRequirements: [
36
+ {
37
+ id: "FR-001",
38
+ text: "The system MUST test"
39
+ }
40
+ ]
41
+ },
42
+ null,
43
+ 2
44
+ )
45
+ );
46
+
47
+ fs.writeFileSync(
48
+ path.join(govDir, "traceability.json"),
49
+ JSON.stringify(
50
+ {
51
+ feature: "test-feature",
52
+ version: 1,
53
+ rows: [
54
+ {
55
+ requirementId: "FR-001",
56
+ acceptanceCriteriaIds: ["AC-001"],
57
+ status: "not_started"
58
+ }
59
+ ]
60
+ },
61
+ null,
62
+ 2
63
+ )
64
+ );
65
+ }
66
+
67
+ function createInvalidFiles() {
68
+ fs.writeFileSync(
69
+ path.join(govDir, "spec.json"),
70
+ JSON.stringify(
71
+ {
72
+ feature: "test-feature",
73
+ version: 1,
74
+ overview: {},
75
+ userStories: []
76
+ },
77
+ null,
78
+ 2
79
+ )
80
+ );
81
+ }
82
+
83
+ function runValidator() {
84
+ try {
85
+ execSync(`node "${validatorPath}"`, { stdio: "pipe" });
86
+ return true;
87
+ } catch (error) {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ function testValidFiles() {
93
+ console.log("\n🧪 Test 1: Valid governance files should pass");
94
+ createValidFiles();
95
+
96
+ const passed = runValidator();
97
+
98
+ if (passed) {
99
+ console.log("āœ… PASS: Validator accepted valid files.");
100
+ } else {
101
+ console.log("āŒ FAIL: Validator rejected valid files.");
102
+ }
103
+
104
+ return passed;
105
+ }
106
+
107
+ function testInvalidFiles() {
108
+ console.log("\n🧪 Test 2: Invalid governance files should fail");
109
+ createInvalidFiles();
110
+
111
+ const passed = runValidator();
112
+
113
+ if (!passed) {
114
+ console.log("āœ… PASS: Validator rejected invalid files.");
115
+ } else {
116
+ console.log("āŒ FAIL: Validator accepted invalid files.");
117
+ }
118
+
119
+ return !passed;
120
+ }
121
+
122
+ function cleanup() {
123
+ if (fs.existsSync(govDir)) {
124
+ fs.rmSync(govDir, { recursive: true, force: true });
125
+ }
126
+ console.log("\n🧹 Cleaned up test files.");
127
+ }
128
+
129
+ function main() {
130
+ console.log("=== SpecDrive Validator Tests ===\n");
131
+
132
+ let allPassed = true;
133
+
134
+ allPassed &= testValidFiles();
135
+ allPassed &= testInvalidFiles();
136
+
137
+ cleanup();
138
+
139
+ if (allPassed) {
140
+ console.log("\nāœ… All validator tests passed.");
141
+ process.exit(0);
142
+ } else {
143
+ console.log("\nāŒ Some validator tests failed.");
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ main();