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.
- package/README.md +955 -697
- package/agents/00-onboarding.md +261 -0
- package/agents/01-constitution.md +214 -201
- package/agents/02-specification.md +249 -226
- package/agents/03-uiux.md +156 -144
- package/agents/04-cascade.md +151 -122
- package/agents/05-discover-skills.md +136 -136
- package/agents/06-documentation.md +158 -145
- package/agents/07-implementation.md +201 -169
- package/agents/08-performance.md +179 -165
- package/agents/09-review-complete.md +239 -168
- package/agents/10-security.md +180 -167
- package/agents/11-test.md +195 -0
- package/commands/gates.js +73 -73
- package/commands/manifest.json +113 -95
- package/commands/permissions.json +39 -0
- package/commands/router.js +151 -127
- package/commands/tools.json +19 -19
- package/dashboard/app.js +394 -0
- package/dashboard/index.html +74 -0
- package/dashboard/server.js +166 -0
- package/dashboard/style.css +157 -0
- package/mcp/mcp.json +31 -0
- package/mcp/server.js +108 -0
- package/package.json +35 -32
- package/schemas/config.schema.json +20 -0
- package/schemas/workflow-state.schema.json +149 -38
- package/scripts/anti-redundancy.js +176 -176
- package/scripts/audit-log.js +46 -46
- package/scripts/check-permission.js +87 -0
- package/scripts/diff-spec.js +50 -50
- package/scripts/diff-version.js +96 -0
- package/scripts/generate-adapters.js +80 -80
- package/scripts/generate-from-template.js +97 -97
- package/scripts/generate-openapi.js +75 -75
- package/scripts/github-team-sync.js +80 -80
- package/scripts/install-hooks.js +20 -20
- package/scripts/load-plugins.js +65 -65
- package/scripts/migrate-openspec.js +318 -0
- package/scripts/migrate-speckit.js +322 -0
- package/scripts/migrate.js +12 -62
- package/scripts/onboard.js +312 -0
- package/scripts/pre-commit.js +56 -20
- package/scripts/team.js +113 -113
- package/scripts/test-adapters.js +118 -118
- package/scripts/test-create.js +13 -13
- package/scripts/test-end-to-end.js +137 -137
- package/scripts/test-router.js +110 -110
- package/scripts/test-state-transitions.js +146 -146
- package/scripts/test-validator.js +152 -152
- package/scripts/validate-config.js +36 -0
- package/scripts/validate-governance.js +150 -130
- package/scripts/verify.js +525 -0
- package/scripts/version-new.js +202 -0
- package/src/index.js +1010 -807
- package/templates/expo/plan.json +12 -0
- package/templates/expo/spec.json +12 -0
- package/templates/expo/tasks.json +5 -0
- package/templates/fastapi/plan.json +12 -0
- package/templates/fastapi/spec.json +12 -0
- package/templates/fastapi/tasks.json +5 -0
- package/templates/generic/plan.json +12 -0
- package/templates/generic/spec.json +11 -0
- package/templates/generic/tasks.json +5 -0
- package/templates/nextjs/plan.json +23 -0
- package/templates/nextjs/spec.json +12 -0
- package/templates/nextjs/tasks.json +5 -0
- package/templates/react-node/plan.json +15 -0
- package/templates/react-node/spec.json +12 -0
- package/templates/react-node/tasks.json +5 -0
- package/templates/registry.json +30 -0
- package/templates/turborepo/plan.json +12 -0
- package/templates/turborepo/spec.json +12 -0
- package/templates/turborepo/tasks.json +5 -0
|
@@ -1,147 +1,147 @@
|
|
|
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
|
-
|
|
8
|
-
function resetState() {
|
|
9
|
-
const initialState = {
|
|
10
|
-
features: [
|
|
11
|
-
{
|
|
12
|
-
name: "test-feature",
|
|
13
|
-
phase: "backlog",
|
|
14
|
-
currentStep: "specification",
|
|
15
|
-
gates: {
|
|
16
|
-
specification: "pending",
|
|
17
|
-
planning: "pending",
|
|
18
|
-
implementation: "pending",
|
|
19
|
-
review: "pending"
|
|
20
|
-
},
|
|
21
|
-
history: [
|
|
22
|
-
{
|
|
23
|
-
event: "feature_created",
|
|
24
|
-
timestamp: new Date().toISOString()
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
fs.writeFileSync(workflowStatePath, JSON.stringify(initialState, null, 2));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function readState() {
|
|
35
|
-
return JSON.parse(fs.readFileSync(workflowStatePath, "utf8"));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function approveGate(gate) {
|
|
39
|
-
try {
|
|
40
|
-
execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} approve ${gate}`, {
|
|
41
|
-
stdio: "pipe"
|
|
42
|
-
});
|
|
43
|
-
return true;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function testInitialState() {
|
|
50
|
-
console.log("\n๐งช Test 1: Initial state should be pending");
|
|
51
|
-
resetState();
|
|
52
|
-
const state = readState();
|
|
53
|
-
const feature = state.features[0];
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
feature.gates.specification === "pending" &&
|
|
57
|
-
feature.gates.planning === "pending" &&
|
|
58
|
-
feature.gates.implementation === "pending" &&
|
|
59
|
-
feature.gates.review === "pending"
|
|
60
|
-
) {
|
|
61
|
-
console.log("โ
PASS: All gates are pending initially.");
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
console.log("โ FAIL: Initial gates are not pending.");
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function testGateApproval() {
|
|
70
|
-
console.log("\n๐งช Test 2: Gate approval should update state");
|
|
71
|
-
resetState();
|
|
72
|
-
|
|
73
|
-
const passed = approveGate("specification");
|
|
74
|
-
|
|
75
|
-
if (!passed) {
|
|
76
|
-
console.log("โ FAIL: Could not approve gate.");
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const state = readState();
|
|
81
|
-
const feature = state.features[0];
|
|
82
|
-
|
|
83
|
-
if (feature.gates.specification === "approved") {
|
|
84
|
-
console.log("โ
PASS: specification gate approved.");
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
console.log("โ FAIL: specification gate not approved.");
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function testInvalidGate() {
|
|
93
|
-
console.log("\n๐งช Test 3: Invalid gate should be rejected");
|
|
94
|
-
resetState();
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} approve invalid-gate`, {
|
|
98
|
-
stdio: "pipe"
|
|
99
|
-
});
|
|
100
|
-
console.log("โ FAIL: Invalid gate was accepted.");
|
|
101
|
-
return false;
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.log("โ
PASS: Invalid gate was rejected.");
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function testHistory() {
|
|
109
|
-
console.log("\n๐งช Test 4: History should record approvals");
|
|
110
|
-
resetState();
|
|
111
|
-
|
|
112
|
-
approveGate("specification");
|
|
113
|
-
|
|
114
|
-
const state = readState();
|
|
115
|
-
const feature = state.features[0];
|
|
116
|
-
|
|
117
|
-
if (feature.history.some((event) => event.event === "specification_approved")) {
|
|
118
|
-
console.log("โ
PASS: History recorded approval.");
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
console.log("โ FAIL: History did not record approval.");
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function main() {
|
|
127
|
-
console.log("=== SpecDrive State Transition Tests ===\n");
|
|
128
|
-
|
|
129
|
-
let allPassed = true;
|
|
130
|
-
|
|
131
|
-
allPassed &= testInitialState();
|
|
132
|
-
allPassed &= testGateApproval();
|
|
133
|
-
allPassed &= testInvalidGate();
|
|
134
|
-
allPassed &= testHistory();
|
|
135
|
-
|
|
136
|
-
resetState();
|
|
137
|
-
|
|
138
|
-
if (allPassed) {
|
|
139
|
-
console.log("\nโ
All state transition tests passed.");
|
|
140
|
-
process.exit(0);
|
|
141
|
-
} else {
|
|
142
|
-
console.log("\nโ Some state transition tests failed.");
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
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
|
+
|
|
8
|
+
function resetState() {
|
|
9
|
+
const initialState = {
|
|
10
|
+
features: [
|
|
11
|
+
{
|
|
12
|
+
name: "test-feature",
|
|
13
|
+
phase: "backlog",
|
|
14
|
+
currentStep: "specification",
|
|
15
|
+
gates: {
|
|
16
|
+
specification: "pending",
|
|
17
|
+
planning: "pending",
|
|
18
|
+
implementation: "pending",
|
|
19
|
+
review: "pending"
|
|
20
|
+
},
|
|
21
|
+
history: [
|
|
22
|
+
{
|
|
23
|
+
event: "feature_created",
|
|
24
|
+
timestamp: new Date().toISOString()
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(workflowStatePath, JSON.stringify(initialState, null, 2));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readState() {
|
|
35
|
+
return JSON.parse(fs.readFileSync(workflowStatePath, "utf8"));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function approveGate(gate) {
|
|
39
|
+
try {
|
|
40
|
+
execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} approve ${gate}`, {
|
|
41
|
+
stdio: "pipe"
|
|
42
|
+
});
|
|
43
|
+
return true;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function testInitialState() {
|
|
50
|
+
console.log("\n๐งช Test 1: Initial state should be pending");
|
|
51
|
+
resetState();
|
|
52
|
+
const state = readState();
|
|
53
|
+
const feature = state.features[0];
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
feature.gates.specification === "pending" &&
|
|
57
|
+
feature.gates.planning === "pending" &&
|
|
58
|
+
feature.gates.implementation === "pending" &&
|
|
59
|
+
feature.gates.review === "pending"
|
|
60
|
+
) {
|
|
61
|
+
console.log("โ
PASS: All gates are pending initially.");
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log("โ FAIL: Initial gates are not pending.");
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function testGateApproval() {
|
|
70
|
+
console.log("\n๐งช Test 2: Gate approval should update state");
|
|
71
|
+
resetState();
|
|
72
|
+
|
|
73
|
+
const passed = approveGate("specification");
|
|
74
|
+
|
|
75
|
+
if (!passed) {
|
|
76
|
+
console.log("โ FAIL: Could not approve gate.");
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const state = readState();
|
|
81
|
+
const feature = state.features[0];
|
|
82
|
+
|
|
83
|
+
if (feature.gates.specification === "approved") {
|
|
84
|
+
console.log("โ
PASS: specification gate approved.");
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log("โ FAIL: specification gate not approved.");
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function testInvalidGate() {
|
|
93
|
+
console.log("\n๐งช Test 3: Invalid gate should be rejected");
|
|
94
|
+
resetState();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
execSync(`node ${path.join(projectRoot, ".sdrive", "src", "index.js")} approve invalid-gate`, {
|
|
98
|
+
stdio: "pipe"
|
|
99
|
+
});
|
|
100
|
+
console.log("โ FAIL: Invalid gate was accepted.");
|
|
101
|
+
return false;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log("โ
PASS: Invalid gate was rejected.");
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function testHistory() {
|
|
109
|
+
console.log("\n๐งช Test 4: History should record approvals");
|
|
110
|
+
resetState();
|
|
111
|
+
|
|
112
|
+
approveGate("specification");
|
|
113
|
+
|
|
114
|
+
const state = readState();
|
|
115
|
+
const feature = state.features[0];
|
|
116
|
+
|
|
117
|
+
if (feature.history.some((event) => event.event === "specification_approved")) {
|
|
118
|
+
console.log("โ
PASS: History recorded approval.");
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log("โ FAIL: History did not record approval.");
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function main() {
|
|
127
|
+
console.log("=== SpecDrive State Transition Tests ===\n");
|
|
128
|
+
|
|
129
|
+
let allPassed = true;
|
|
130
|
+
|
|
131
|
+
allPassed &= testInitialState();
|
|
132
|
+
allPassed &= testGateApproval();
|
|
133
|
+
allPassed &= testInvalidGate();
|
|
134
|
+
allPassed &= testHistory();
|
|
135
|
+
|
|
136
|
+
resetState();
|
|
137
|
+
|
|
138
|
+
if (allPassed) {
|
|
139
|
+
console.log("\nโ
All state transition tests passed.");
|
|
140
|
+
process.exit(0);
|
|
141
|
+
} else {
|
|
142
|
+
console.log("\nโ Some state transition tests failed.");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
147
|
main();
|
|
@@ -1,153 +1,153 @@
|
|
|
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
|
-
title: "Test Feature",
|
|
18
|
-
version: 1,
|
|
19
|
-
overview: {
|
|
20
|
-
problemStatement: "Test problem statement",
|
|
21
|
-
proposedSolution: "Test proposed solution",
|
|
22
|
-
targetAudience: "Test users"
|
|
23
|
-
},
|
|
24
|
-
userStories: [
|
|
25
|
-
{
|
|
26
|
-
id: "US-001",
|
|
27
|
-
title: "Test story",
|
|
28
|
-
role: "user",
|
|
29
|
-
want: "test",
|
|
30
|
-
benefit: "test",
|
|
31
|
-
acceptanceCriteria: [
|
|
32
|
-
{
|
|
33
|
-
id: "AC-001",
|
|
34
|
-
text: "Given test, when test, then test"
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
functionalRequirements: [
|
|
40
|
-
{
|
|
41
|
-
id: "FR-001",
|
|
42
|
-
text: "The system MUST test"
|
|
43
|
-
}
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
null,
|
|
47
|
-
2
|
|
48
|
-
)
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
fs.writeFileSync(
|
|
52
|
-
path.join(govDir, "traceability.json"),
|
|
53
|
-
JSON.stringify(
|
|
54
|
-
{
|
|
55
|
-
feature: "test-feature",
|
|
56
|
-
version: 1,
|
|
57
|
-
rows: [
|
|
58
|
-
{
|
|
59
|
-
requirementId: "FR-001",
|
|
60
|
-
acceptanceCriteriaIds: ["AC-001"],
|
|
61
|
-
taskIds: ["T1.1"],
|
|
62
|
-
status: "not_started"
|
|
63
|
-
}
|
|
64
|
-
]
|
|
65
|
-
},
|
|
66
|
-
null,
|
|
67
|
-
2
|
|
68
|
-
)
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function createInvalidFiles() {
|
|
73
|
-
fs.writeFileSync(
|
|
74
|
-
path.join(govDir, "spec.json"),
|
|
75
|
-
JSON.stringify(
|
|
76
|
-
{
|
|
77
|
-
feature: "test-feature",
|
|
78
|
-
version: 1,
|
|
79
|
-
overview: {},
|
|
80
|
-
userStories: []
|
|
81
|
-
},
|
|
82
|
-
null,
|
|
83
|
-
2
|
|
84
|
-
)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function runValidator() {
|
|
89
|
-
try {
|
|
90
|
-
execSync(`node "${validatorPath}"`, { stdio: "pipe" });
|
|
91
|
-
return true;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function testValidFiles() {
|
|
98
|
-
console.log("\n๐งช Test 1: Valid governance files should pass");
|
|
99
|
-
createValidFiles();
|
|
100
|
-
|
|
101
|
-
const passed = runValidator();
|
|
102
|
-
|
|
103
|
-
if (passed) {
|
|
104
|
-
console.log("โ
PASS: Validator accepted valid files.");
|
|
105
|
-
} else {
|
|
106
|
-
console.log("โ FAIL: Validator rejected valid files.");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return passed;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function testInvalidFiles() {
|
|
113
|
-
console.log("\n๐งช Test 2: Invalid governance files should fail");
|
|
114
|
-
createInvalidFiles();
|
|
115
|
-
|
|
116
|
-
const passed = runValidator();
|
|
117
|
-
|
|
118
|
-
if (!passed) {
|
|
119
|
-
console.log("โ
PASS: Validator rejected invalid files.");
|
|
120
|
-
} else {
|
|
121
|
-
console.log("โ FAIL: Validator accepted invalid files.");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return !passed;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function cleanup() {
|
|
128
|
-
if (fs.existsSync(govDir)) {
|
|
129
|
-
fs.rmSync(govDir, { recursive: true, force: true });
|
|
130
|
-
}
|
|
131
|
-
console.log("\n๐งน Cleaned up test files.");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function main() {
|
|
135
|
-
console.log("=== SpecDrive Validator Tests ===\n");
|
|
136
|
-
|
|
137
|
-
let allPassed = true;
|
|
138
|
-
|
|
139
|
-
allPassed &= testValidFiles();
|
|
140
|
-
allPassed &= testInvalidFiles();
|
|
141
|
-
|
|
142
|
-
cleanup();
|
|
143
|
-
|
|
144
|
-
if (allPassed) {
|
|
145
|
-
console.log("\nโ
All validator tests passed.");
|
|
146
|
-
process.exit(0);
|
|
147
|
-
} else {
|
|
148
|
-
console.log("\nโ Some validator tests failed.");
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
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
|
+
title: "Test Feature",
|
|
18
|
+
version: 1,
|
|
19
|
+
overview: {
|
|
20
|
+
problemStatement: "Test problem statement",
|
|
21
|
+
proposedSolution: "Test proposed solution",
|
|
22
|
+
targetAudience: "Test users"
|
|
23
|
+
},
|
|
24
|
+
userStories: [
|
|
25
|
+
{
|
|
26
|
+
id: "US-001",
|
|
27
|
+
title: "Test story",
|
|
28
|
+
role: "user",
|
|
29
|
+
want: "test",
|
|
30
|
+
benefit: "test",
|
|
31
|
+
acceptanceCriteria: [
|
|
32
|
+
{
|
|
33
|
+
id: "AC-001",
|
|
34
|
+
text: "Given test, when test, then test"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
functionalRequirements: [
|
|
40
|
+
{
|
|
41
|
+
id: "FR-001",
|
|
42
|
+
text: "The system MUST test"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
null,
|
|
47
|
+
2
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
fs.writeFileSync(
|
|
52
|
+
path.join(govDir, "traceability.json"),
|
|
53
|
+
JSON.stringify(
|
|
54
|
+
{
|
|
55
|
+
feature: "test-feature",
|
|
56
|
+
version: 1,
|
|
57
|
+
rows: [
|
|
58
|
+
{
|
|
59
|
+
requirementId: "FR-001",
|
|
60
|
+
acceptanceCriteriaIds: ["AC-001"],
|
|
61
|
+
taskIds: ["T1.1"],
|
|
62
|
+
status: "not_started"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
null,
|
|
67
|
+
2
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createInvalidFiles() {
|
|
73
|
+
fs.writeFileSync(
|
|
74
|
+
path.join(govDir, "spec.json"),
|
|
75
|
+
JSON.stringify(
|
|
76
|
+
{
|
|
77
|
+
feature: "test-feature",
|
|
78
|
+
version: 1,
|
|
79
|
+
overview: {},
|
|
80
|
+
userStories: []
|
|
81
|
+
},
|
|
82
|
+
null,
|
|
83
|
+
2
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runValidator() {
|
|
89
|
+
try {
|
|
90
|
+
execSync(`node "${validatorPath}"`, { stdio: "pipe" });
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function testValidFiles() {
|
|
98
|
+
console.log("\n๐งช Test 1: Valid governance files should pass");
|
|
99
|
+
createValidFiles();
|
|
100
|
+
|
|
101
|
+
const passed = runValidator();
|
|
102
|
+
|
|
103
|
+
if (passed) {
|
|
104
|
+
console.log("โ
PASS: Validator accepted valid files.");
|
|
105
|
+
} else {
|
|
106
|
+
console.log("โ FAIL: Validator rejected valid files.");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return passed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function testInvalidFiles() {
|
|
113
|
+
console.log("\n๐งช Test 2: Invalid governance files should fail");
|
|
114
|
+
createInvalidFiles();
|
|
115
|
+
|
|
116
|
+
const passed = runValidator();
|
|
117
|
+
|
|
118
|
+
if (!passed) {
|
|
119
|
+
console.log("โ
PASS: Validator rejected invalid files.");
|
|
120
|
+
} else {
|
|
121
|
+
console.log("โ FAIL: Validator accepted invalid files.");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return !passed;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cleanup() {
|
|
128
|
+
if (fs.existsSync(govDir)) {
|
|
129
|
+
fs.rmSync(govDir, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
console.log("\n๐งน Cleaned up test files.");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function main() {
|
|
135
|
+
console.log("=== SpecDrive Validator Tests ===\n");
|
|
136
|
+
|
|
137
|
+
let allPassed = true;
|
|
138
|
+
|
|
139
|
+
allPassed &= testValidFiles();
|
|
140
|
+
allPassed &= testInvalidFiles();
|
|
141
|
+
|
|
142
|
+
cleanup();
|
|
143
|
+
|
|
144
|
+
if (allPassed) {
|
|
145
|
+
console.log("\nโ
All validator tests passed.");
|
|
146
|
+
process.exit(0);
|
|
147
|
+
} else {
|
|
148
|
+
console.log("\nโ Some validator tests failed.");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
153
|
main();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const Ajv = require("ajv");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const projectRoot = process.cwd();
|
|
8
|
+
|
|
9
|
+
const configPath = path.join(projectRoot, ".sdrive", "config.json");
|
|
10
|
+
const schemaPath = path.join(projectRoot, ".sdrive", "schemas", "config.schema.json");
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(configPath)) {
|
|
13
|
+
console.error("โ config.json not found. Run `sdrive init` first.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(schemaPath)) {
|
|
18
|
+
console.error("โ config.schema.json not found.");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ajv = new Ajv({ allErrors: true });
|
|
23
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
24
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
25
|
+
|
|
26
|
+
const validate = ajv.compile(schema);
|
|
27
|
+
const valid = validate(config);
|
|
28
|
+
|
|
29
|
+
if (valid) {
|
|
30
|
+
console.log("โ
config.json is valid.");
|
|
31
|
+
process.exit(0);
|
|
32
|
+
} else {
|
|
33
|
+
console.error("โ config.json is INVALID:");
|
|
34
|
+
console.error(validate.errors);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|