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
package/scripts/pre-commit.js
CHANGED
|
@@ -1,20 +1,56 @@
|
|
|
1
|
-
const { execSync } = require("child_process");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
|
|
5
|
-
const projectRoot = process.cwd();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
const projectRoot = process.cwd();
|
|
6
|
+
|
|
7
|
+
const validatorPath = path.join(projectRoot, ".sdrive", "scripts", "validate-governance.js");
|
|
8
|
+
const verifyPath = path.join(projectRoot, ".sdrive", "scripts", "verify.js");
|
|
9
|
+
|
|
10
|
+
function getFeatureNames() {
|
|
11
|
+
const workflowStatePath = path.join(projectRoot, ".sdrive", "workflow-state.json");
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(workflowStatePath)) return [];
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const state = JSON.parse(fs.readFileSync(workflowStatePath, "utf8"));
|
|
17
|
+
return (state.features || []).map((f) => f.name);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 1. Run governance validator
|
|
24
|
+
if (fs.existsSync(validatorPath)) {
|
|
25
|
+
try {
|
|
26
|
+
execSync(`node "${validatorPath}"`, { stdio: "inherit" });
|
|
27
|
+
console.log("✅ Governance validation passed.");
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("❌ Governance validation failed. Fix errors before committing.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
console.log("⚠️ Validator not found. Skipping governance validation.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Run spec-to-code verification for every active feature
|
|
37
|
+
const features = getFeatureNames();
|
|
38
|
+
|
|
39
|
+
if (features.length === 0) {
|
|
40
|
+
console.log("⚠️ No features found. Skipping spec-to-code verification.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const feature of features) {
|
|
45
|
+
try {
|
|
46
|
+
execSync(`node "${verifyPath}" "${feature}"`, { stdio: "pipe" });
|
|
47
|
+
console.log(`✅ Spec-to-code verification passed for ${feature}.`);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`❌ Verification failed for ${feature}.`);
|
|
50
|
+
console.error(error.stdout ? error.stdout.toString() : "");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log("✅ Pre-commit checks passed.");
|
|
56
|
+
process.exit(0);
|
package/scripts/team.js
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const projectRoot = process.cwd();
|
|
5
|
-
const teamPath = path.join(projectRoot, ".sdrive", "team.json");
|
|
6
|
-
|
|
7
|
-
function loadTeam() {
|
|
8
|
-
if (!fs.existsSync(teamPath)) {
|
|
9
|
-
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error(`❌ Invalid team.json: ${error.message}`);
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function saveTeam(team) {
|
|
22
|
-
fs.writeFileSync(teamPath, JSON.stringify(team, null, 2));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getUserRole(username) {
|
|
26
|
-
const team = loadTeam();
|
|
27
|
-
const member = team.members?.find((m) => m.username === username);
|
|
28
|
-
return member ? member.role : null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function canApprove(username) {
|
|
32
|
-
const role = getUserRole(username);
|
|
33
|
-
if (!role) return false;
|
|
34
|
-
|
|
35
|
-
const team = loadTeam();
|
|
36
|
-
return team.rules?.approval_required?.includes(role) || false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function canImplement(username) {
|
|
40
|
-
const role = getUserRole(username);
|
|
41
|
-
if (!role) return false;
|
|
42
|
-
|
|
43
|
-
const team = loadTeam();
|
|
44
|
-
return team.rules?.implementation_allowed?.includes(role) || false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function listMembers() {
|
|
48
|
-
const team = loadTeam();
|
|
49
|
-
return team.members || [];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function addMember(username, role) {
|
|
53
|
-
const team = loadTeam();
|
|
54
|
-
|
|
55
|
-
if (!team.members) {
|
|
56
|
-
team.members = [];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const existing = team.members.find((m) => m.username === username);
|
|
60
|
-
if (existing) {
|
|
61
|
-
console.error(`❌ User "${username}" already exists.`);
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
team.members.push({ username, role });
|
|
66
|
-
saveTeam(team);
|
|
67
|
-
console.log(`✅ Added ${username} as ${role}.`);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function removeMember(username) {
|
|
72
|
-
const team = loadTeam();
|
|
73
|
-
|
|
74
|
-
const before = team.members?.length || 0;
|
|
75
|
-
team.members = (team.members || []).filter((m) => m.username !== username);
|
|
76
|
-
const after = team.members.length;
|
|
77
|
-
|
|
78
|
-
if (before === after) {
|
|
79
|
-
console.error(`❌ User "${username}" not found.`);
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
saveTeam(team);
|
|
84
|
-
console.log(`✅ Removed ${username}.`);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function updateMember(username, role) {
|
|
89
|
-
const team = loadTeam();
|
|
90
|
-
|
|
91
|
-
const member = team.members?.find((m) => m.username === username);
|
|
92
|
-
|
|
93
|
-
if (!member) {
|
|
94
|
-
console.error(`❌ User "${username}" not found.`);
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
member.role = role;
|
|
99
|
-
saveTeam(team);
|
|
100
|
-
console.log(`✅ Updated ${username} to ${role}.`);
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
module.exports = {
|
|
105
|
-
loadTeam,
|
|
106
|
-
saveTeam,
|
|
107
|
-
getUserRole,
|
|
108
|
-
canApprove,
|
|
109
|
-
canImplement,
|
|
110
|
-
listMembers,
|
|
111
|
-
addMember,
|
|
112
|
-
removeMember,
|
|
113
|
-
updateMember
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
const teamPath = path.join(projectRoot, ".sdrive", "team.json");
|
|
6
|
+
|
|
7
|
+
function loadTeam() {
|
|
8
|
+
if (!fs.existsSync(teamPath)) {
|
|
9
|
+
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(`❌ Invalid team.json: ${error.message}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function saveTeam(team) {
|
|
22
|
+
fs.writeFileSync(teamPath, JSON.stringify(team, null, 2));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getUserRole(username) {
|
|
26
|
+
const team = loadTeam();
|
|
27
|
+
const member = team.members?.find((m) => m.username === username);
|
|
28
|
+
return member ? member.role : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function canApprove(username) {
|
|
32
|
+
const role = getUserRole(username);
|
|
33
|
+
if (!role) return false;
|
|
34
|
+
|
|
35
|
+
const team = loadTeam();
|
|
36
|
+
return team.rules?.approval_required?.includes(role) || false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function canImplement(username) {
|
|
40
|
+
const role = getUserRole(username);
|
|
41
|
+
if (!role) return false;
|
|
42
|
+
|
|
43
|
+
const team = loadTeam();
|
|
44
|
+
return team.rules?.implementation_allowed?.includes(role) || false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function listMembers() {
|
|
48
|
+
const team = loadTeam();
|
|
49
|
+
return team.members || [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function addMember(username, role) {
|
|
53
|
+
const team = loadTeam();
|
|
54
|
+
|
|
55
|
+
if (!team.members) {
|
|
56
|
+
team.members = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const existing = team.members.find((m) => m.username === username);
|
|
60
|
+
if (existing) {
|
|
61
|
+
console.error(`❌ User "${username}" already exists.`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
team.members.push({ username, role });
|
|
66
|
+
saveTeam(team);
|
|
67
|
+
console.log(`✅ Added ${username} as ${role}.`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function removeMember(username) {
|
|
72
|
+
const team = loadTeam();
|
|
73
|
+
|
|
74
|
+
const before = team.members?.length || 0;
|
|
75
|
+
team.members = (team.members || []).filter((m) => m.username !== username);
|
|
76
|
+
const after = team.members.length;
|
|
77
|
+
|
|
78
|
+
if (before === after) {
|
|
79
|
+
console.error(`❌ User "${username}" not found.`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
saveTeam(team);
|
|
84
|
+
console.log(`✅ Removed ${username}.`);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function updateMember(username, role) {
|
|
89
|
+
const team = loadTeam();
|
|
90
|
+
|
|
91
|
+
const member = team.members?.find((m) => m.username === username);
|
|
92
|
+
|
|
93
|
+
if (!member) {
|
|
94
|
+
console.error(`❌ User "${username}" not found.`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
member.role = role;
|
|
99
|
+
saveTeam(team);
|
|
100
|
+
console.log(`✅ Updated ${username} to ${role}.`);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
loadTeam,
|
|
106
|
+
saveTeam,
|
|
107
|
+
getUserRole,
|
|
108
|
+
canApprove,
|
|
109
|
+
canImplement,
|
|
110
|
+
listMembers,
|
|
111
|
+
addMember,
|
|
112
|
+
removeMember,
|
|
113
|
+
updateMember
|
|
114
114
|
};
|
package/scripts/test-adapters.js
CHANGED
|
@@ -1,119 +1,119 @@
|
|
|
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 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 toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
|
|
19
|
-
const tools = JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
|
|
20
|
-
|
|
21
|
-
for (const tool of tools) {
|
|
22
|
-
const fullPath = path.join(projectRoot, tool.adapterPath);
|
|
23
|
-
|
|
24
|
-
if (fs.existsSync(fullPath)) {
|
|
25
|
-
console.log(` ✅ Found: ${tool.adapterPath}`);
|
|
26
|
-
} else {
|
|
27
|
-
console.log(` ❌ Missing: ${tool.adapterPath}`);
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log("✅ PASS: All adapters created.");
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function testAdapterContent() {
|
|
37
|
-
console.log("\n🧪 Test 2: Adapter content includes SpecDrive instructions");
|
|
38
|
-
|
|
39
|
-
const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
|
|
40
|
-
|
|
41
|
-
if (!fs.existsSync(adapterPath)) {
|
|
42
|
-
console.log("❌ FAIL: Copilot adapter missing.");
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const content = fs.readFileSync(adapterPath, "utf8");
|
|
47
|
-
|
|
48
|
-
if (content.includes(".sdrive/commands/manifest.json")) {
|
|
49
|
-
console.log("✅ PASS: Adapter contains manifest reference.");
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log("❌ FAIL: Adapter missing manifest reference.");
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function testSafetyRule() {
|
|
58
|
-
console.log("\n🧪 Test 3: Generator does not overwrite existing files");
|
|
59
|
-
|
|
60
|
-
const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
|
|
61
|
-
|
|
62
|
-
fs.writeFileSync(adapterPath, "# Custom existing file\n", "utf8");
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
execSync(`node "${generatorPath}"`, { stdio: "pipe" });
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.log("❌ FAIL: Generator failed.");
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const content = fs.readFileSync(adapterPath, "utf8");
|
|
72
|
-
|
|
73
|
-
if (content === "# Custom existing file\n") {
|
|
74
|
-
console.log("✅ PASS: Existing file was not overwritten.");
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log("❌ FAIL: Existing file was overwritten.");
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function cleanup() {
|
|
83
|
-
const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
|
|
84
|
-
|
|
85
|
-
if (fs.existsSync(toolsPath)) {
|
|
86
|
-
const tools = JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
|
|
87
|
-
|
|
88
|
-
tools.forEach((tool) => {
|
|
89
|
-
const fullPath = path.join(projectRoot, tool.adapterPath);
|
|
90
|
-
if (fs.existsSync(fullPath)) {
|
|
91
|
-
fs.rmSync(fullPath, { force: true });
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
console.log("\n🧹 Cleaned up adapters.");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function main() {
|
|
100
|
-
console.log("=== SpecDrive Adapter Tests ===\n");
|
|
101
|
-
|
|
102
|
-
let allPassed = true;
|
|
103
|
-
|
|
104
|
-
allPassed &= testGeneratorCreatesAdapters();
|
|
105
|
-
allPassed &= testAdapterContent();
|
|
106
|
-
allPassed &= testSafetyRule();
|
|
107
|
-
|
|
108
|
-
cleanup();
|
|
109
|
-
|
|
110
|
-
if (allPassed) {
|
|
111
|
-
console.log("\n✅ All adapter tests passed.");
|
|
112
|
-
process.exit(0);
|
|
113
|
-
} else {
|
|
114
|
-
console.log("\n❌ Some adapter tests failed.");
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
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 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 toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
|
|
19
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
|
|
20
|
+
|
|
21
|
+
for (const tool of tools) {
|
|
22
|
+
const fullPath = path.join(projectRoot, tool.adapterPath);
|
|
23
|
+
|
|
24
|
+
if (fs.existsSync(fullPath)) {
|
|
25
|
+
console.log(` ✅ Found: ${tool.adapterPath}`);
|
|
26
|
+
} else {
|
|
27
|
+
console.log(` ❌ Missing: ${tool.adapterPath}`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log("✅ PASS: All adapters created.");
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function testAdapterContent() {
|
|
37
|
+
console.log("\n🧪 Test 2: Adapter content includes SpecDrive instructions");
|
|
38
|
+
|
|
39
|
+
const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(adapterPath)) {
|
|
42
|
+
console.log("❌ FAIL: Copilot adapter missing.");
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const content = fs.readFileSync(adapterPath, "utf8");
|
|
47
|
+
|
|
48
|
+
if (content.includes(".sdrive/commands/manifest.json")) {
|
|
49
|
+
console.log("✅ PASS: Adapter contains manifest reference.");
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log("❌ FAIL: Adapter missing manifest reference.");
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function testSafetyRule() {
|
|
58
|
+
console.log("\n🧪 Test 3: Generator does not overwrite existing files");
|
|
59
|
+
|
|
60
|
+
const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(adapterPath, "# Custom existing file\n", "utf8");
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
execSync(`node "${generatorPath}"`, { stdio: "pipe" });
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.log("❌ FAIL: Generator failed.");
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const content = fs.readFileSync(adapterPath, "utf8");
|
|
72
|
+
|
|
73
|
+
if (content === "# Custom existing file\n") {
|
|
74
|
+
console.log("✅ PASS: Existing file was not overwritten.");
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log("❌ FAIL: Existing file was overwritten.");
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function cleanup() {
|
|
83
|
+
const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
|
|
84
|
+
|
|
85
|
+
if (fs.existsSync(toolsPath)) {
|
|
86
|
+
const tools = JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
|
|
87
|
+
|
|
88
|
+
tools.forEach((tool) => {
|
|
89
|
+
const fullPath = path.join(projectRoot, tool.adapterPath);
|
|
90
|
+
if (fs.existsSync(fullPath)) {
|
|
91
|
+
fs.rmSync(fullPath, { force: true });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log("\n🧹 Cleaned up adapters.");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function main() {
|
|
100
|
+
console.log("=== SpecDrive Adapter Tests ===\n");
|
|
101
|
+
|
|
102
|
+
let allPassed = true;
|
|
103
|
+
|
|
104
|
+
allPassed &= testGeneratorCreatesAdapters();
|
|
105
|
+
allPassed &= testAdapterContent();
|
|
106
|
+
allPassed &= testSafetyRule();
|
|
107
|
+
|
|
108
|
+
cleanup();
|
|
109
|
+
|
|
110
|
+
if (allPassed) {
|
|
111
|
+
console.log("\n✅ All adapter tests passed.");
|
|
112
|
+
process.exit(0);
|
|
113
|
+
} else {
|
|
114
|
+
console.log("\n❌ Some adapter tests failed.");
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
119
|
main();
|
package/scripts/test-create.js
CHANGED
|
@@ -1,14 +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);
|
|
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
14
|
}
|