specdrive-cli 0.1.2 → 0.1.8
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 +35 -14
- package/commands/gates.js +26 -16
- package/commands/router.js +2 -41
- package/commands/tools.json +15 -20
- package/package.json +1 -1
- package/schemas/plan.schema.json +94 -16
- package/schemas/spec.schema.json +140 -17
- package/schemas/tasks.schema.json +57 -14
- package/schemas/traceability.schema.json +41 -12
- package/schemas/workflow-state.schema.json +40 -13
- package/scripts/anti-redundancy.js +11 -11
- package/scripts/audit-log.js +14 -2
- package/scripts/generate-adapters.js +28 -28
- package/scripts/generate-from-template.js +1 -0
- package/scripts/load-plugins.js +15 -6
- package/scripts/team.js +79 -6
- package/scripts/test-adapters.js +20 -29
- package/scripts/test-end-to-end.js +1 -4
- package/scripts/test-router.js +10 -4
- package/scripts/test-state-transitions.js +0 -1
- package/scripts/test-validator.js +7 -2
- package/scripts/validate-governance.js +29 -64
- package/src/index.js +71 -8
|
@@ -1,38 +1,80 @@
|
|
|
1
1
|
{
|
|
2
|
-
"": "http://json-schema.org/draft-07/schema#",
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "Execution Tasks",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"feature": {
|
|
7
|
-
|
|
6
|
+
"feature": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"pattern": "^[a-z0-9-]+$"
|
|
9
|
+
},
|
|
10
|
+
"version": {
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"minimum": 1
|
|
13
|
+
},
|
|
8
14
|
"phases": {
|
|
9
15
|
"type": "array",
|
|
10
16
|
"minItems": 1,
|
|
11
17
|
"items": {
|
|
12
18
|
"type": "object",
|
|
13
19
|
"properties": {
|
|
14
|
-
"phaseId": {
|
|
15
|
-
|
|
20
|
+
"phaseId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^P[0-9]+$"
|
|
23
|
+
},
|
|
24
|
+
"name": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 3
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "^P[0-9]+$"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
16
35
|
"tasks": {
|
|
17
36
|
"type": "array",
|
|
18
37
|
"minItems": 1,
|
|
19
38
|
"items": {
|
|
20
39
|
"type": "object",
|
|
21
40
|
"properties": {
|
|
22
|
-
"id": {
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
"id": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"pattern": "^T[0-9]+\\.[0-9]+$"
|
|
44
|
+
},
|
|
45
|
+
"title": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"minLength": 5
|
|
48
|
+
},
|
|
49
|
+
"description": {
|
|
50
|
+
"type": "string"
|
|
51
|
+
},
|
|
25
52
|
"linkedRequirements": {
|
|
26
53
|
"type": "array",
|
|
27
|
-
"items": {
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"pattern": "^(FR|NFR)-[0-9]{3}$"
|
|
57
|
+
},
|
|
58
|
+
"minItems": 1
|
|
28
59
|
},
|
|
29
60
|
"linkedAcceptanceCriteria": {
|
|
30
61
|
"type": "array",
|
|
31
|
-
"items": {
|
|
62
|
+
"items": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"pattern": "^AC-[0-9]{3}$"
|
|
65
|
+
}
|
|
32
66
|
},
|
|
33
|
-
"completed": {
|
|
67
|
+
"completed": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"default": false
|
|
70
|
+
}
|
|
34
71
|
},
|
|
35
|
-
"required": [
|
|
72
|
+
"required": [
|
|
73
|
+
"id",
|
|
74
|
+
"title",
|
|
75
|
+
"linkedRequirements",
|
|
76
|
+
"completed"
|
|
77
|
+
]
|
|
36
78
|
}
|
|
37
79
|
}
|
|
38
80
|
},
|
|
@@ -40,5 +82,6 @@
|
|
|
40
82
|
}
|
|
41
83
|
}
|
|
42
84
|
},
|
|
43
|
-
"required": ["feature", "version", "phases"]
|
|
44
|
-
|
|
85
|
+
"required": ["feature", "version", "phases"],
|
|
86
|
+
"additionalProperties": false
|
|
87
|
+
}
|
|
@@ -1,34 +1,63 @@
|
|
|
1
1
|
{
|
|
2
|
-
"": "http://json-schema.org/draft-07/schema#",
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "Traceability Matrix",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
6
|
-
"feature": {
|
|
7
|
-
|
|
6
|
+
"feature": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"pattern": "^[a-z0-9-]+$"
|
|
9
|
+
},
|
|
10
|
+
"version": {
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"minimum": 1
|
|
13
|
+
},
|
|
8
14
|
"rows": {
|
|
9
15
|
"type": "array",
|
|
16
|
+
"minItems": 1,
|
|
10
17
|
"items": {
|
|
11
18
|
"type": "object",
|
|
12
19
|
"properties": {
|
|
13
|
-
"requirementId": {
|
|
20
|
+
"requirementId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^(FR|NFR)-[0-9]{3}$"
|
|
23
|
+
},
|
|
14
24
|
"acceptanceCriteriaIds": {
|
|
15
25
|
"type": "array",
|
|
16
|
-
"items": {
|
|
26
|
+
"items": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"pattern": "^AC-[0-9]{3}$"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"planSection": {
|
|
32
|
+
"type": "string"
|
|
17
33
|
},
|
|
18
|
-
"planSection": { "type": "string" },
|
|
19
34
|
"taskIds": {
|
|
20
35
|
"type": "array",
|
|
21
|
-
"items": {
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"pattern": "^T[0-9]+\\.[0-9]+$"
|
|
39
|
+
}
|
|
22
40
|
},
|
|
23
41
|
"testIds": {
|
|
24
42
|
"type": "array",
|
|
25
|
-
"items": {
|
|
43
|
+
"items": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"pattern": "^TEST-[0-9]{3}$"
|
|
46
|
+
}
|
|
26
47
|
},
|
|
27
|
-
"status": {
|
|
48
|
+
"status": {
|
|
49
|
+
"enum": ["not_started", "in_progress", "complete"]
|
|
50
|
+
}
|
|
28
51
|
},
|
|
29
|
-
"required": [
|
|
52
|
+
"required": [
|
|
53
|
+
"requirementId",
|
|
54
|
+
"acceptanceCriteriaIds",
|
|
55
|
+
"taskIds",
|
|
56
|
+
"status"
|
|
57
|
+
]
|
|
30
58
|
}
|
|
31
59
|
}
|
|
32
60
|
},
|
|
33
|
-
"required": ["feature", "version", "rows"]
|
|
34
|
-
|
|
61
|
+
"required": ["feature", "version", "rows"],
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"": "http://json-schema.org/draft-07/schema#",
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "Workflow State",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -8,26 +8,52 @@
|
|
|
8
8
|
"items": {
|
|
9
9
|
"type": "object",
|
|
10
10
|
"properties": {
|
|
11
|
-
"name": {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
"name": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"pattern": "^[a-z0-9-]+$"
|
|
14
|
+
},
|
|
15
|
+
"phase": {
|
|
16
|
+
"enum": ["backlog", "ongoing", "completed", "blocked"]
|
|
17
|
+
},
|
|
18
|
+
"currentStep": {
|
|
19
|
+
"enum": ["specification", "planning", "implementation", "review"]
|
|
20
|
+
},
|
|
14
21
|
"gates": {
|
|
15
22
|
"type": "object",
|
|
16
23
|
"properties": {
|
|
17
|
-
"specification": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
24
|
+
"specification": {
|
|
25
|
+
"enum": ["pending", "approved", "rejected"]
|
|
26
|
+
},
|
|
27
|
+
"planning": {
|
|
28
|
+
"enum": ["pending", "approved", "rejected"]
|
|
29
|
+
},
|
|
30
|
+
"implementation": {
|
|
31
|
+
"enum": ["pending", "in_progress", "approved", "rejected"]
|
|
32
|
+
},
|
|
33
|
+
"review": {
|
|
34
|
+
"enum": ["pending", "approved", "rejected"]
|
|
35
|
+
}
|
|
21
36
|
},
|
|
22
|
-
"required": [
|
|
37
|
+
"required": [
|
|
38
|
+
"specification",
|
|
39
|
+
"planning",
|
|
40
|
+
"implementation",
|
|
41
|
+
"review"
|
|
42
|
+
]
|
|
23
43
|
},
|
|
24
44
|
"history": {
|
|
25
45
|
"type": "array",
|
|
26
46
|
"items": {
|
|
27
47
|
"type": "object",
|
|
28
48
|
"properties": {
|
|
29
|
-
"event": {
|
|
30
|
-
|
|
49
|
+
"event": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"minLength": 3
|
|
52
|
+
},
|
|
53
|
+
"timestamp": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"format": "date-time"
|
|
56
|
+
}
|
|
31
57
|
},
|
|
32
58
|
"required": ["event", "timestamp"]
|
|
33
59
|
}
|
|
@@ -37,5 +63,6 @@
|
|
|
37
63
|
}
|
|
38
64
|
}
|
|
39
65
|
},
|
|
40
|
-
"required": ["features"]
|
|
41
|
-
|
|
66
|
+
"required": ["features"],
|
|
67
|
+
"additionalProperties": false
|
|
68
|
+
}
|
|
@@ -24,7 +24,7 @@ function getInstalledDependencies() {
|
|
|
24
24
|
];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function listFiles(dir, extensions = [".ts", ".tsx", ".js", ".jsx"]) {
|
|
27
|
+
function listFiles(dir, extensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]) {
|
|
28
28
|
if (!fs.existsSync(dir)) return [];
|
|
29
29
|
|
|
30
30
|
let results = [];
|
|
@@ -97,35 +97,35 @@ function checkRedundancy(plannedItems) {
|
|
|
97
97
|
|
|
98
98
|
switch (item.type) {
|
|
99
99
|
case "component":
|
|
100
|
-
exists = existingAssets.components.some((
|
|
100
|
+
exists = existingAssets.components.some((p) => p.includes(item.name));
|
|
101
101
|
if (exists) {
|
|
102
|
-
location = existingAssets.components.find((
|
|
102
|
+
location = existingAssets.components.find((p) => p.includes(item.name));
|
|
103
103
|
}
|
|
104
104
|
break;
|
|
105
105
|
case "service":
|
|
106
106
|
case "api":
|
|
107
|
-
exists = existingAssets.services.some((
|
|
107
|
+
exists = existingAssets.services.some((p) => p.includes(item.name));
|
|
108
108
|
if (exists) {
|
|
109
|
-
location = existingAssets.services.find((
|
|
109
|
+
location = existingAssets.services.find((p) => p.includes(item.name));
|
|
110
110
|
}
|
|
111
111
|
break;
|
|
112
112
|
case "hook":
|
|
113
|
-
exists = existingAssets.hooks.some((
|
|
113
|
+
exists = existingAssets.hooks.some((p) => p.includes(item.name));
|
|
114
114
|
if (exists) {
|
|
115
|
-
location = existingAssets.hooks.find((
|
|
115
|
+
location = existingAssets.hooks.find((p) => p.includes(item.name));
|
|
116
116
|
}
|
|
117
117
|
break;
|
|
118
118
|
case "utility":
|
|
119
|
-
exists = existingAssets.utils.some((
|
|
119
|
+
exists = existingAssets.utils.some((p) => p.includes(item.name));
|
|
120
120
|
if (exists) {
|
|
121
|
-
location = existingAssets.utils.find((
|
|
121
|
+
location = existingAssets.utils.find((p) => p.includes(item.name));
|
|
122
122
|
}
|
|
123
123
|
break;
|
|
124
124
|
case "model":
|
|
125
125
|
case "entity":
|
|
126
|
-
exists = existingAssets.models.some((
|
|
126
|
+
exists = existingAssets.models.some((p) => p.includes(item.name));
|
|
127
127
|
if (exists) {
|
|
128
|
-
location = existingAssets.models.find((
|
|
128
|
+
location = existingAssets.models.find((p) => p.includes(item.name));
|
|
129
129
|
}
|
|
130
130
|
break;
|
|
131
131
|
case "package":
|
package/scripts/audit-log.js
CHANGED
|
@@ -8,7 +8,13 @@ function loadAuditLog() {
|
|
|
8
8
|
if (!fs.existsSync(auditLogPath)) {
|
|
9
9
|
return { events: [] };
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(fs.readFileSync(auditLogPath, "utf8"));
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`⚠️ Could not parse audit log: ${error.message}`);
|
|
16
|
+
return { events: [] };
|
|
17
|
+
}
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
function saveAuditLog(log) {
|
|
@@ -28,8 +34,14 @@ function logEvent(event, details = {}) {
|
|
|
28
34
|
console.log(`📝 Audit: ${event}`);
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
function getRecentEvents(count = 10) {
|
|
38
|
+
const log = loadAuditLog();
|
|
39
|
+
return log.events.slice(-count).reverse();
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
module.exports = {
|
|
32
43
|
logEvent,
|
|
33
44
|
loadAuditLog,
|
|
34
|
-
saveAuditLog
|
|
45
|
+
saveAuditLog,
|
|
46
|
+
getRecentEvents
|
|
35
47
|
};
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// SAFETY RULE
|
|
2
|
-
// NEVER overwrite existing files in .github/
|
|
3
|
-
// If a file exists, ask the user before modifying it.
|
|
4
|
-
// Only create new files if missing.
|
|
5
|
-
|
|
6
1
|
const fs = require("fs");
|
|
7
2
|
const path = require("path");
|
|
8
3
|
|
|
@@ -35,26 +30,11 @@ When the user types a /sdrive:* command, follow these steps:
|
|
|
35
30
|
5. Run validators after writing files
|
|
36
31
|
6. Stop at human approval gates
|
|
37
32
|
|
|
38
|
-
## Command list
|
|
39
|
-
|
|
40
|
-
- /sdrive:constitution — Create or update project constitution
|
|
41
|
-
- /sdrive:propose — Create spec, traceability, plan, and tasks
|
|
42
|
-
- /sdrive:design — Build UI/UX prototype
|
|
43
|
-
- /sdrive:apply — Write production code
|
|
44
|
-
- /sdrive:review — Final audit and merge
|
|
45
|
-
- /sdrive:secure — Security audit
|
|
46
|
-
- /sdrive:performance — Performance benchmark
|
|
47
|
-
- /sdrive:docs — Generate documentation
|
|
48
|
-
- /sdrive:sync — Cascade sync
|
|
49
|
-
- /sdrive:skills — Manage AI skills
|
|
50
|
-
|
|
51
33
|
Always read the manifest first. Never hardcode the command list.
|
|
52
34
|
`;
|
|
53
35
|
}
|
|
54
36
|
|
|
55
|
-
function
|
|
56
|
-
const tools = loadTools();
|
|
57
|
-
|
|
37
|
+
function generateForTools(tools) {
|
|
58
38
|
tools.forEach((tool) => {
|
|
59
39
|
const adapterFullPath = path.join(projectRoot, tool.adapterPath);
|
|
60
40
|
const adapterDir = path.dirname(adapterFullPath);
|
|
@@ -63,19 +43,39 @@ function generate() {
|
|
|
63
43
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
64
44
|
}
|
|
65
45
|
|
|
66
|
-
// SAFETY GUARD
|
|
67
46
|
if (fs.existsSync(adapterFullPath)) {
|
|
68
|
-
console.log(`⚠️ Skipped existing
|
|
69
|
-
console.log(" SpecDrive never overwrites existing project files.");
|
|
70
|
-
console.log(" Delete the file or rename it to regenerate.\n");
|
|
47
|
+
console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
|
|
71
48
|
return;
|
|
72
49
|
}
|
|
73
50
|
|
|
74
51
|
fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
|
|
75
|
-
console.log(`✅ Generated
|
|
52
|
+
console.log(`✅ Generated: ${tool.adapterPath}`);
|
|
76
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
|
+
}
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
generateForTools(selectedTools);
|
|
74
|
+
console.log("\n🎉 Selected adapters generated successfully.");
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
module.exports = {
|
|
78
|
+
generateAll,
|
|
79
|
+
generateSelected,
|
|
80
|
+
loadTools
|
|
81
|
+
};
|
package/scripts/load-plugins.js
CHANGED
|
@@ -22,11 +22,15 @@ function loadPlugins() {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
try {
|
|
26
|
+
const pluginDef = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
|
|
27
|
+
plugins.push({
|
|
28
|
+
...pluginDef,
|
|
29
|
+
path: path.join(pluginsDir, folder)
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.warn(`⚠️ Skipping invalid plugin: ${folder}`);
|
|
33
|
+
}
|
|
30
34
|
});
|
|
31
35
|
|
|
32
36
|
return plugins;
|
|
@@ -51,7 +55,12 @@ function runPlugin(pluginName) {
|
|
|
51
55
|
require(entryPath);
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
function listPlugins() {
|
|
59
|
+
return loadPlugins();
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
module.exports = {
|
|
55
63
|
loadPlugins,
|
|
56
|
-
runPlugin
|
|
64
|
+
runPlugin,
|
|
65
|
+
listPlugins
|
|
57
66
|
};
|
package/scripts/team.js
CHANGED
|
@@ -6,15 +6,25 @@ const teamPath = path.join(projectRoot, ".sdrive", "team.json");
|
|
|
6
6
|
|
|
7
7
|
function loadTeam() {
|
|
8
8
|
if (!fs.existsSync(teamPath)) {
|
|
9
|
-
console.error("❌ team.json not found.");
|
|
9
|
+
console.error("❌ team.json not found. Run `sdrive init` first.");
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
|
-
|
|
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));
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
function getUserRole(username) {
|
|
16
26
|
const team = loadTeam();
|
|
17
|
-
const member = team.members
|
|
27
|
+
const member = team.members?.find((m) => m.username === username);
|
|
18
28
|
return member ? member.role : null;
|
|
19
29
|
}
|
|
20
30
|
|
|
@@ -23,7 +33,7 @@ function canApprove(username) {
|
|
|
23
33
|
if (!role) return false;
|
|
24
34
|
|
|
25
35
|
const team = loadTeam();
|
|
26
|
-
return team.rules
|
|
36
|
+
return team.rules?.approval_required?.includes(role) || false;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
function canImplement(username) {
|
|
@@ -31,11 +41,74 @@ function canImplement(username) {
|
|
|
31
41
|
if (!role) return false;
|
|
32
42
|
|
|
33
43
|
const team = loadTeam();
|
|
34
|
-
return team.rules
|
|
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;
|
|
35
102
|
}
|
|
36
103
|
|
|
37
104
|
module.exports = {
|
|
105
|
+
loadTeam,
|
|
106
|
+
saveTeam,
|
|
38
107
|
getUserRole,
|
|
39
108
|
canApprove,
|
|
40
|
-
canImplement
|
|
109
|
+
canImplement,
|
|
110
|
+
listMembers,
|
|
111
|
+
addMember,
|
|
112
|
+
removeMember,
|
|
113
|
+
updateMember
|
|
41
114
|
};
|
package/scripts/test-adapters.js
CHANGED
|
@@ -6,7 +6,7 @@ const projectRoot = process.cwd();
|
|
|
6
6
|
const generatorPath = path.join(projectRoot, ".sdrive", "scripts", "generate-adapters.js");
|
|
7
7
|
|
|
8
8
|
function testGeneratorCreatesAdapters() {
|
|
9
|
-
console.log("\n🧪 Test 1: Generator creates
|
|
9
|
+
console.log("\n🧪 Test 1: Generator creates adapters");
|
|
10
10
|
|
|
11
11
|
try {
|
|
12
12
|
execSync(`node "${generatorPath}"`, { stdio: "pipe" });
|
|
@@ -15,20 +15,16 @@ function testGeneratorCreatesAdapters() {
|
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
".vscode/sdrive-commands.json"
|
|
24
|
-
];
|
|
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);
|
|
25
23
|
|
|
26
|
-
for (const adapter of expectedAdapters) {
|
|
27
|
-
const fullPath = path.join(projectRoot, adapter);
|
|
28
24
|
if (fs.existsSync(fullPath)) {
|
|
29
|
-
console.log(` ✅ Found: ${
|
|
25
|
+
console.log(` ✅ Found: ${tool.adapterPath}`);
|
|
30
26
|
} else {
|
|
31
|
-
console.log(` ❌ Missing: ${
|
|
27
|
+
console.log(` ❌ Missing: ${tool.adapterPath}`);
|
|
32
28
|
return false;
|
|
33
29
|
}
|
|
34
30
|
}
|
|
@@ -63,10 +59,8 @@ function testSafetyRule() {
|
|
|
63
59
|
|
|
64
60
|
const adapterPath = path.join(projectRoot, ".github", "copilot-instructions.md");
|
|
65
61
|
|
|
66
|
-
// Modify the file
|
|
67
62
|
fs.writeFileSync(adapterPath, "# Custom existing file\n", "utf8");
|
|
68
63
|
|
|
69
|
-
// Run generator
|
|
70
64
|
try {
|
|
71
65
|
execSync(`node "${generatorPath}"`, { stdio: "pipe" });
|
|
72
66
|
} catch (error) {
|
|
@@ -74,7 +68,6 @@ function testSafetyRule() {
|
|
|
74
68
|
return false;
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
// Check content was not overwritten
|
|
78
71
|
const content = fs.readFileSync(adapterPath, "utf8");
|
|
79
72
|
|
|
80
73
|
if (content === "# Custom existing file\n") {
|
|
@@ -87,20 +80,18 @@ function testSafetyRule() {
|
|
|
87
80
|
}
|
|
88
81
|
|
|
89
82
|
function cleanup() {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
});
|
|
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
|
+
}
|
|
104
95
|
|
|
105
96
|
console.log("\n🧹 Cleaned up adapters.");
|
|
106
97
|
}
|