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,177 +1,177 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const projectRoot = process.cwd();
|
|
5
|
-
|
|
6
|
-
function readJson(filePath) {
|
|
7
|
-
try {
|
|
8
|
-
if (fs.existsSync(filePath)) {
|
|
9
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
10
|
-
}
|
|
11
|
-
} catch (error) {
|
|
12
|
-
console.error(`⚠️ Could not parse ${filePath}: ${error.message}`);
|
|
13
|
-
}
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getInstalledDependencies() {
|
|
18
|
-
const packageJson = readJson(path.join(projectRoot, "package.json"));
|
|
19
|
-
if (!packageJson) return [];
|
|
20
|
-
|
|
21
|
-
return [
|
|
22
|
-
...Object.keys(packageJson.dependencies || {}),
|
|
23
|
-
...Object.keys(packageJson.devDependencies || {})
|
|
24
|
-
];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function listFiles(dir, extensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]) {
|
|
28
|
-
if (!fs.existsSync(dir)) return [];
|
|
29
|
-
|
|
30
|
-
let results = [];
|
|
31
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
32
|
-
|
|
33
|
-
for (const entry of entries) {
|
|
34
|
-
const fullPath = path.join(dir, entry.name);
|
|
35
|
-
if (entry.isDirectory()) {
|
|
36
|
-
results = results.concat(listFiles(fullPath, extensions));
|
|
37
|
-
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
38
|
-
results.push(fullPath);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return results;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function detectExistingAssets() {
|
|
46
|
-
const srcDir = path.join(projectRoot, "src");
|
|
47
|
-
const files = listFiles(srcDir);
|
|
48
|
-
|
|
49
|
-
const assets = {
|
|
50
|
-
components: [],
|
|
51
|
-
services: [],
|
|
52
|
-
hooks: [],
|
|
53
|
-
utils: [],
|
|
54
|
-
api: [],
|
|
55
|
-
models: [],
|
|
56
|
-
constants: []
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
files.forEach((file) => {
|
|
60
|
-
const relative = path.relative(projectRoot, file).replace(/\\/g, "/");
|
|
61
|
-
|
|
62
|
-
if (relative.includes("/components/") || relative.includes("/ui/")) {
|
|
63
|
-
assets.components.push(relative);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (relative.includes("/services/") || relative.includes("/api/")) {
|
|
67
|
-
assets.services.push(relative);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (relative.includes("/hooks/") || relative.startsWith("use-")) {
|
|
71
|
-
assets.hooks.push(relative);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (relative.includes("/utils/") || relative.includes("/helpers/")) {
|
|
75
|
-
assets.utils.push(relative);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (relative.includes("/models/") || relative.includes("/entities/")) {
|
|
79
|
-
assets.models.push(relative);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (relative.includes("/constants/") || relative.includes("/config/")) {
|
|
83
|
-
assets.constants.push(relative);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
return assets;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function checkRedundancy(plannedItems) {
|
|
91
|
-
const existingAssets = detectExistingAssets();
|
|
92
|
-
const installedDependencies = getInstalledDependencies();
|
|
93
|
-
|
|
94
|
-
const report = plannedItems.map((item) => {
|
|
95
|
-
let exists = false;
|
|
96
|
-
let location = null;
|
|
97
|
-
|
|
98
|
-
switch (item.type) {
|
|
99
|
-
case "component":
|
|
100
|
-
exists = existingAssets.components.some((p) => p.includes(item.name));
|
|
101
|
-
if (exists) {
|
|
102
|
-
location = existingAssets.components.find((p) => p.includes(item.name));
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
case "service":
|
|
106
|
-
case "api":
|
|
107
|
-
exists = existingAssets.services.some((p) => p.includes(item.name));
|
|
108
|
-
if (exists) {
|
|
109
|
-
location = existingAssets.services.find((p) => p.includes(item.name));
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
case "hook":
|
|
113
|
-
exists = existingAssets.hooks.some((p) => p.includes(item.name));
|
|
114
|
-
if (exists) {
|
|
115
|
-
location = existingAssets.hooks.find((p) => p.includes(item.name));
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
case "utility":
|
|
119
|
-
exists = existingAssets.utils.some((p) => p.includes(item.name));
|
|
120
|
-
if (exists) {
|
|
121
|
-
location = existingAssets.utils.find((p) => p.includes(item.name));
|
|
122
|
-
}
|
|
123
|
-
break;
|
|
124
|
-
case "model":
|
|
125
|
-
case "entity":
|
|
126
|
-
exists = existingAssets.models.some((p) => p.includes(item.name));
|
|
127
|
-
if (exists) {
|
|
128
|
-
location = existingAssets.models.find((p) => p.includes(item.name));
|
|
129
|
-
}
|
|
130
|
-
break;
|
|
131
|
-
case "package":
|
|
132
|
-
case "dependency":
|
|
133
|
-
exists = installedDependencies.includes(item.name);
|
|
134
|
-
location = exists ? "package.json" : null;
|
|
135
|
-
break;
|
|
136
|
-
default:
|
|
137
|
-
exists = false;
|
|
138
|
-
location = null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
name: item.name,
|
|
143
|
-
type: item.type,
|
|
144
|
-
exists,
|
|
145
|
-
action: exists ? "reuse" : "create",
|
|
146
|
-
location
|
|
147
|
-
};
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
feature: "current",
|
|
152
|
-
items: report,
|
|
153
|
-
existingAssets,
|
|
154
|
-
installedDependencies
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function main() {
|
|
159
|
-
const plannedItemsPath = process.argv[2];
|
|
160
|
-
|
|
161
|
-
if (!plannedItemsPath) {
|
|
162
|
-
console.error("❌ Usage: node anti-redundancy.js <planned-items.json>");
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const plannedItems = readJson(path.resolve(plannedItemsPath));
|
|
167
|
-
|
|
168
|
-
if (!plannedItems || !Array.isArray(plannedItems)) {
|
|
169
|
-
console.error("❌ planned-items.json must contain an array of items.");
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const report = checkRedundancy(plannedItems);
|
|
174
|
-
console.log(JSON.stringify(report, null, 2));
|
|
175
|
-
}
|
|
176
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
|
|
6
|
+
function readJson(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
if (fs.existsSync(filePath)) {
|
|
9
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
10
|
+
}
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(`⚠️ Could not parse ${filePath}: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getInstalledDependencies() {
|
|
18
|
+
const packageJson = readJson(path.join(projectRoot, "package.json"));
|
|
19
|
+
if (!packageJson) return [];
|
|
20
|
+
|
|
21
|
+
return [
|
|
22
|
+
...Object.keys(packageJson.dependencies || {}),
|
|
23
|
+
...Object.keys(packageJson.devDependencies || {})
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function listFiles(dir, extensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]) {
|
|
28
|
+
if (!fs.existsSync(dir)) return [];
|
|
29
|
+
|
|
30
|
+
let results = [];
|
|
31
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
32
|
+
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const fullPath = path.join(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
results = results.concat(listFiles(fullPath, extensions));
|
|
37
|
+
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
38
|
+
results.push(fullPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return results;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function detectExistingAssets() {
|
|
46
|
+
const srcDir = path.join(projectRoot, "src");
|
|
47
|
+
const files = listFiles(srcDir);
|
|
48
|
+
|
|
49
|
+
const assets = {
|
|
50
|
+
components: [],
|
|
51
|
+
services: [],
|
|
52
|
+
hooks: [],
|
|
53
|
+
utils: [],
|
|
54
|
+
api: [],
|
|
55
|
+
models: [],
|
|
56
|
+
constants: []
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
files.forEach((file) => {
|
|
60
|
+
const relative = path.relative(projectRoot, file).replace(/\\/g, "/");
|
|
61
|
+
|
|
62
|
+
if (relative.includes("/components/") || relative.includes("/ui/")) {
|
|
63
|
+
assets.components.push(relative);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (relative.includes("/services/") || relative.includes("/api/")) {
|
|
67
|
+
assets.services.push(relative);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (relative.includes("/hooks/") || relative.startsWith("use-")) {
|
|
71
|
+
assets.hooks.push(relative);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (relative.includes("/utils/") || relative.includes("/helpers/")) {
|
|
75
|
+
assets.utils.push(relative);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (relative.includes("/models/") || relative.includes("/entities/")) {
|
|
79
|
+
assets.models.push(relative);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (relative.includes("/constants/") || relative.includes("/config/")) {
|
|
83
|
+
assets.constants.push(relative);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return assets;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkRedundancy(plannedItems) {
|
|
91
|
+
const existingAssets = detectExistingAssets();
|
|
92
|
+
const installedDependencies = getInstalledDependencies();
|
|
93
|
+
|
|
94
|
+
const report = plannedItems.map((item) => {
|
|
95
|
+
let exists = false;
|
|
96
|
+
let location = null;
|
|
97
|
+
|
|
98
|
+
switch (item.type) {
|
|
99
|
+
case "component":
|
|
100
|
+
exists = existingAssets.components.some((p) => p.includes(item.name));
|
|
101
|
+
if (exists) {
|
|
102
|
+
location = existingAssets.components.find((p) => p.includes(item.name));
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case "service":
|
|
106
|
+
case "api":
|
|
107
|
+
exists = existingAssets.services.some((p) => p.includes(item.name));
|
|
108
|
+
if (exists) {
|
|
109
|
+
location = existingAssets.services.find((p) => p.includes(item.name));
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
case "hook":
|
|
113
|
+
exists = existingAssets.hooks.some((p) => p.includes(item.name));
|
|
114
|
+
if (exists) {
|
|
115
|
+
location = existingAssets.hooks.find((p) => p.includes(item.name));
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case "utility":
|
|
119
|
+
exists = existingAssets.utils.some((p) => p.includes(item.name));
|
|
120
|
+
if (exists) {
|
|
121
|
+
location = existingAssets.utils.find((p) => p.includes(item.name));
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
case "model":
|
|
125
|
+
case "entity":
|
|
126
|
+
exists = existingAssets.models.some((p) => p.includes(item.name));
|
|
127
|
+
if (exists) {
|
|
128
|
+
location = existingAssets.models.find((p) => p.includes(item.name));
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
case "package":
|
|
132
|
+
case "dependency":
|
|
133
|
+
exists = installedDependencies.includes(item.name);
|
|
134
|
+
location = exists ? "package.json" : null;
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
exists = false;
|
|
138
|
+
location = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
name: item.name,
|
|
143
|
+
type: item.type,
|
|
144
|
+
exists,
|
|
145
|
+
action: exists ? "reuse" : "create",
|
|
146
|
+
location
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
feature: "current",
|
|
152
|
+
items: report,
|
|
153
|
+
existingAssets,
|
|
154
|
+
installedDependencies
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function main() {
|
|
159
|
+
const plannedItemsPath = process.argv[2];
|
|
160
|
+
|
|
161
|
+
if (!plannedItemsPath) {
|
|
162
|
+
console.error("❌ Usage: node anti-redundancy.js <planned-items.json>");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const plannedItems = readJson(path.resolve(plannedItemsPath));
|
|
167
|
+
|
|
168
|
+
if (!plannedItems || !Array.isArray(plannedItems)) {
|
|
169
|
+
console.error("❌ planned-items.json must contain an array of items.");
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const report = checkRedundancy(plannedItems);
|
|
174
|
+
console.log(JSON.stringify(report, null, 2));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
177
|
main();
|
package/scripts/audit-log.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const projectRoot = process.cwd();
|
|
5
|
-
const auditLogPath = path.join(projectRoot, ".sdrive", "audit-log.json");
|
|
6
|
-
|
|
7
|
-
function loadAuditLog() {
|
|
8
|
-
if (!fs.existsSync(auditLogPath)) {
|
|
9
|
-
return { events: [] };
|
|
10
|
-
}
|
|
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
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function saveAuditLog(log) {
|
|
21
|
-
fs.writeFileSync(auditLogPath, JSON.stringify(log, null, 2));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function logEvent(event, details = {}) {
|
|
25
|
-
const log = loadAuditLog();
|
|
26
|
-
|
|
27
|
-
log.events.push({
|
|
28
|
-
timestamp: new Date().toISOString(),
|
|
29
|
-
...details,
|
|
30
|
-
event
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
saveAuditLog(log);
|
|
34
|
-
console.log(`📝 Audit: ${event}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getRecentEvents(count = 10) {
|
|
38
|
-
const log = loadAuditLog();
|
|
39
|
-
return log.events.slice(-count).reverse();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
module.exports = {
|
|
43
|
-
logEvent,
|
|
44
|
-
loadAuditLog,
|
|
45
|
-
saveAuditLog,
|
|
46
|
-
getRecentEvents
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
const auditLogPath = path.join(projectRoot, ".sdrive", "audit-log.json");
|
|
6
|
+
|
|
7
|
+
function loadAuditLog() {
|
|
8
|
+
if (!fs.existsSync(auditLogPath)) {
|
|
9
|
+
return { events: [] };
|
|
10
|
+
}
|
|
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
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function saveAuditLog(log) {
|
|
21
|
+
fs.writeFileSync(auditLogPath, JSON.stringify(log, null, 2));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function logEvent(event, details = {}) {
|
|
25
|
+
const log = loadAuditLog();
|
|
26
|
+
|
|
27
|
+
log.events.push({
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
...details,
|
|
30
|
+
event
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
saveAuditLog(log);
|
|
34
|
+
console.log(`📝 Audit: ${event}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getRecentEvents(count = 10) {
|
|
38
|
+
const log = loadAuditLog();
|
|
39
|
+
return log.events.slice(-count).reverse();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
logEvent,
|
|
44
|
+
loadAuditLog,
|
|
45
|
+
saveAuditLog,
|
|
46
|
+
getRecentEvents
|
|
47
47
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const projectRoot = process.cwd();
|
|
8
|
+
|
|
9
|
+
function getCurrentUser() {
|
|
10
|
+
try {
|
|
11
|
+
return execSync("git config user.name", { encoding: "utf8" }).trim();
|
|
12
|
+
} catch (error) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function loadTeam() {
|
|
18
|
+
const teamPath = path.join(projectRoot, ".sdrive", "team.json");
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(teamPath)) {
|
|
21
|
+
console.error("❌ team.json not found.");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return JSON.parse(fs.readFileSync(teamPath, "utf8"));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadPermissions() {
|
|
29
|
+
const permPath = path.join(projectRoot, ".sdrive", "commands", "permissions.json");
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(permPath)) {
|
|
32
|
+
console.error("❌ permissions.json not found.");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return JSON.parse(fs.readFileSync(permPath, "utf8"));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getUserRole(username) {
|
|
40
|
+
const team = loadTeam();
|
|
41
|
+
const member = team.members?.find((m) => m.username === username);
|
|
42
|
+
return member ? member.role : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkPermission(commandType, commandName) {
|
|
46
|
+
const username = getCurrentUser();
|
|
47
|
+
|
|
48
|
+
if (!username) {
|
|
49
|
+
console.error("❌ Could not detect Git user. Set git config user.name.");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const role = getUserRole(username);
|
|
54
|
+
|
|
55
|
+
if (!role) {
|
|
56
|
+
console.error(`❌ User "${username}" is not in team.json.`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const permissions = loadPermissions();
|
|
61
|
+
const rules = commandType === "cli" ? permissions.cli : permissions.rules;
|
|
62
|
+
const allowedRoles = rules[commandName];
|
|
63
|
+
|
|
64
|
+
if (!allowedRoles) {
|
|
65
|
+
console.log(`⚠️ No permission rule for ${commandType}:${commandName}. Allowing by default.`);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!allowedRoles.includes(role)) {
|
|
70
|
+
console.error(`❌ User "${username}" (${role}) is not allowed to run ${commandType}:${commandName}.`);
|
|
71
|
+
console.error(` Allowed roles: ${allowedRoles.join(", ")}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`✅ User "${username}" (${role}) is allowed.`);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const commandType = process.argv[2];
|
|
80
|
+
const commandName = process.argv[3];
|
|
81
|
+
|
|
82
|
+
if (!commandType || !commandName) {
|
|
83
|
+
console.error("❌ Usage: node check-permission.js <cli|slash> <command>");
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
checkPermission(commandType, commandName);
|
package/scripts/diff-spec.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const projectRoot = process.cwd();
|
|
5
|
-
|
|
6
|
-
function readLines(filePath) {
|
|
7
|
-
if (!fs.existsSync(filePath)) {
|
|
8
|
-
return [];
|
|
9
|
-
}
|
|
10
|
-
return fs.readFileSync(filePath, "utf8").split("\n");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function simpleDiff(oldFile, newFile) {
|
|
14
|
-
const oldLines = readLines(oldFile);
|
|
15
|
-
const newLines = readLines(newFile);
|
|
16
|
-
|
|
17
|
-
const oldSet = new Set(oldLines);
|
|
18
|
-
const newSet = new Set(newLines);
|
|
19
|
-
|
|
20
|
-
const added = newLines.filter((line) => !oldSet.has(line));
|
|
21
|
-
const removed = oldLines.filter((line) => !newSet.has(line));
|
|
22
|
-
|
|
23
|
-
console.log(`\n📄 Diff between:`);
|
|
24
|
-
console.log(` Old: ${path.basename(oldFile)}`);
|
|
25
|
-
console.log(` New: ${path.basename(newFile)}\n`);
|
|
26
|
-
|
|
27
|
-
if (added.length === 0 && removed.length === 0) {
|
|
28
|
-
console.log("✅ No changes detected.");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (removed.length > 0) {
|
|
33
|
-
console.log("❌ Removed:");
|
|
34
|
-
removed.forEach((line) => console.log(` - ${line}`));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (added.length > 0) {
|
|
38
|
-
console.log("✅ Added:");
|
|
39
|
-
added.forEach((line) => console.log(` + ${line}`));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const oldFile = process.argv[2];
|
|
44
|
-
const newFile = process.argv[3];
|
|
45
|
-
|
|
46
|
-
if (!oldFile || !newFile) {
|
|
47
|
-
console.error("❌ Usage: node diff-spec.js <old-file> <new-file>");
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
|
|
6
|
+
function readLines(filePath) {
|
|
7
|
+
if (!fs.existsSync(filePath)) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return fs.readFileSync(filePath, "utf8").split("\n");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function simpleDiff(oldFile, newFile) {
|
|
14
|
+
const oldLines = readLines(oldFile);
|
|
15
|
+
const newLines = readLines(newFile);
|
|
16
|
+
|
|
17
|
+
const oldSet = new Set(oldLines);
|
|
18
|
+
const newSet = new Set(newLines);
|
|
19
|
+
|
|
20
|
+
const added = newLines.filter((line) => !oldSet.has(line));
|
|
21
|
+
const removed = oldLines.filter((line) => !newSet.has(line));
|
|
22
|
+
|
|
23
|
+
console.log(`\n📄 Diff between:`);
|
|
24
|
+
console.log(` Old: ${path.basename(oldFile)}`);
|
|
25
|
+
console.log(` New: ${path.basename(newFile)}\n`);
|
|
26
|
+
|
|
27
|
+
if (added.length === 0 && removed.length === 0) {
|
|
28
|
+
console.log("✅ No changes detected.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (removed.length > 0) {
|
|
33
|
+
console.log("❌ Removed:");
|
|
34
|
+
removed.forEach((line) => console.log(` - ${line}`));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (added.length > 0) {
|
|
38
|
+
console.log("✅ Added:");
|
|
39
|
+
added.forEach((line) => console.log(` + ${line}`));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const oldFile = process.argv[2];
|
|
44
|
+
const newFile = process.argv[3];
|
|
45
|
+
|
|
46
|
+
if (!oldFile || !newFile) {
|
|
47
|
+
console.error("❌ Usage: node diff-spec.js <old-file> <new-file>");
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
51
|
simpleDiff(path.resolve(oldFile), path.resolve(newFile));
|