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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const projectRoot = process.cwd();
|
|
7
|
+
const sdriveRoot = path.join(projectRoot, ".sdrive");
|
|
8
|
+
|
|
9
|
+
const featureName = process.argv[2];
|
|
10
|
+
const versionA = process.argv[3];
|
|
11
|
+
const versionB = process.argv[4];
|
|
12
|
+
|
|
13
|
+
if (!featureName || !versionA || !versionB) {
|
|
14
|
+
console.error("❌ Usage: node diff-version.js <feature> <v1> <v2>");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findFile(baseDir, version, fileName) {
|
|
19
|
+
const candidate = path.join(baseDir, featureName, version, fileName);
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(candidate)) {
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readLines(filePath) {
|
|
29
|
+
return fs.readFileSync(filePath, "utf8").split("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function simpleDiff(oldLines, newLines) {
|
|
33
|
+
const added = [];
|
|
34
|
+
const removed = [];
|
|
35
|
+
const unchanged = [];
|
|
36
|
+
|
|
37
|
+
const oldSet = new Set(oldLines);
|
|
38
|
+
const newSet = new Set(newLines);
|
|
39
|
+
|
|
40
|
+
newLines.forEach((line) => {
|
|
41
|
+
if (oldSet.has(line)) {
|
|
42
|
+
unchanged.push(line);
|
|
43
|
+
} else {
|
|
44
|
+
added.push(line);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
oldLines.forEach((line) => {
|
|
49
|
+
if (!newSet.has(line)) {
|
|
50
|
+
removed.push(line);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return { added, removed, unchanged };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function diffFile(fileName, label) {
|
|
58
|
+
const specA = findFile(path.join(sdriveRoot, "specs", "ongoing"), versionA, fileName);
|
|
59
|
+
const specB = findFile(path.join(sdriveRoot, "specs", "ongoing"), versionB, fileName);
|
|
60
|
+
|
|
61
|
+
if (!specA || !specB) {
|
|
62
|
+
console.log(`\n⚠️ ${label}: not both versions have this file. Skipping.`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const oldLines = readLines(specA);
|
|
67
|
+
const newLines = readLines(specB);
|
|
68
|
+
const { added, removed } = simpleDiff(oldLines, newLines);
|
|
69
|
+
|
|
70
|
+
console.log(`\n📄 ${label} (${versionA} → ${versionB})`);
|
|
71
|
+
|
|
72
|
+
if (added.length === 0 && removed.length === 0) {
|
|
73
|
+
console.log(" ✅ No changes.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (removed.length > 0) {
|
|
78
|
+
console.log(" ❌ Removed:");
|
|
79
|
+
removed.forEach((line) => console.log(` - ${line}`));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (added.length > 0) {
|
|
83
|
+
console.log(" ✅ Added:");
|
|
84
|
+
added.forEach((line) => console.log(` + ${line}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function main() {
|
|
89
|
+
console.log(`\n🔍 Diff: ${featureName} (${versionA} → ${versionB})`);
|
|
90
|
+
|
|
91
|
+
diffFile("spec.md", "Specification");
|
|
92
|
+
diffFile("plan.md", "Plan");
|
|
93
|
+
diffFile("tasks.md", "Tasks");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const projectRoot = process.cwd();
|
|
5
|
-
|
|
6
|
-
function loadTools() {
|
|
7
|
-
const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(toolsPath)) {
|
|
10
|
-
console.error("❌ tools.json not found.");
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function adapterContent(toolName) {
|
|
18
|
-
return `# SpecDrive Commands
|
|
19
|
-
|
|
20
|
-
This project uses SpecDrive for spec-driven development.
|
|
21
|
-
|
|
22
|
-
## How to use
|
|
23
|
-
|
|
24
|
-
When the user types a /sdrive:* command, follow these steps:
|
|
25
|
-
|
|
26
|
-
1. Read .sdrive/commands/manifest.json
|
|
27
|
-
2. Find the matching command
|
|
28
|
-
3. Read the agent file listed in agentFile
|
|
29
|
-
4. Follow that agent's instructions exactly
|
|
30
|
-
5. Run validators after writing files
|
|
31
|
-
6. Stop at human approval gates
|
|
32
|
-
|
|
33
|
-
Always read the manifest first. Never hardcode the command list.
|
|
34
|
-
`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function generateForTools(tools) {
|
|
38
|
-
tools.forEach((tool) => {
|
|
39
|
-
const adapterFullPath = path.join(projectRoot, tool.adapterPath);
|
|
40
|
-
const adapterDir = path.dirname(adapterFullPath);
|
|
41
|
-
|
|
42
|
-
if (!fs.existsSync(adapterDir)) {
|
|
43
|
-
fs.mkdirSync(adapterDir, { recursive: true });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (fs.existsSync(adapterFullPath)) {
|
|
47
|
-
console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
|
|
52
|
-
console.log(`✅ Generated: ${tool.adapterPath}`);
|
|
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
|
-
}
|
|
72
|
-
|
|
73
|
-
generateForTools(selectedTools);
|
|
74
|
-
console.log("\n🎉 Selected adapters generated successfully.");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = {
|
|
78
|
-
generateAll,
|
|
79
|
-
generateSelected,
|
|
80
|
-
loadTools
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
|
|
6
|
+
function loadTools() {
|
|
7
|
+
const toolsPath = path.join(projectRoot, ".sdrive", "commands", "tools.json");
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(toolsPath)) {
|
|
10
|
+
console.error("❌ tools.json not found.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return JSON.parse(fs.readFileSync(toolsPath, "utf8")).tools;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function adapterContent(toolName) {
|
|
18
|
+
return `# SpecDrive Commands
|
|
19
|
+
|
|
20
|
+
This project uses SpecDrive for spec-driven development.
|
|
21
|
+
|
|
22
|
+
## How to use
|
|
23
|
+
|
|
24
|
+
When the user types a /sdrive:* command, follow these steps:
|
|
25
|
+
|
|
26
|
+
1. Read .sdrive/commands/manifest.json
|
|
27
|
+
2. Find the matching command
|
|
28
|
+
3. Read the agent file listed in agentFile
|
|
29
|
+
4. Follow that agent's instructions exactly
|
|
30
|
+
5. Run validators after writing files
|
|
31
|
+
6. Stop at human approval gates
|
|
32
|
+
|
|
33
|
+
Always read the manifest first. Never hardcode the command list.
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function generateForTools(tools) {
|
|
38
|
+
tools.forEach((tool) => {
|
|
39
|
+
const adapterFullPath = path.join(projectRoot, tool.adapterPath);
|
|
40
|
+
const adapterDir = path.dirname(adapterFullPath);
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(adapterDir)) {
|
|
43
|
+
fs.mkdirSync(adapterDir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (fs.existsSync(adapterFullPath)) {
|
|
47
|
+
console.log(`⚠️ Skipped existing: ${tool.adapterPath}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fs.writeFileSync(adapterFullPath, adapterContent(tool.name));
|
|
52
|
+
console.log(`✅ Generated: ${tool.adapterPath}`);
|
|
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
|
+
}
|
|
72
|
+
|
|
73
|
+
generateForTools(selectedTools);
|
|
74
|
+
console.log("\n🎉 Selected adapters generated successfully.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
generateAll,
|
|
79
|
+
generateSelected,
|
|
80
|
+
loadTools
|
|
81
81
|
};
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const projectRoot = process.cwd();
|
|
5
|
-
|
|
6
|
-
function loadRegistry() {
|
|
7
|
-
const registryPath = path.join(projectRoot, ".sdrive", "templates", "registry.json");
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(registryPath)) {
|
|
10
|
-
console.error("❌ registry.json not found.");
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return JSON.parse(fs.readFileSync(registryPath, "utf8")).templates;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function templateExists(templateName) {
|
|
18
|
-
const registry = loadRegistry();
|
|
19
|
-
return registry.some((t) => t.name === templateName || t.path === templateName);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function listTemplates() {
|
|
23
|
-
const registry = loadRegistry();
|
|
24
|
-
console.log("\nAvailable SpecDrive Templates\n");
|
|
25
|
-
registry.forEach((t) => {
|
|
26
|
-
console.log(` ${t.name} — ${t.description}`);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function loadTemplate(templateName, fileName) {
|
|
31
|
-
const templatePath = path.join(projectRoot, ".sdrive", "templates", templateName, fileName);
|
|
32
|
-
return fs.readFileSync(templatePath, "utf8");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function applyVariables(content, variables) {
|
|
36
|
-
let result = content;
|
|
37
|
-
Object.keys(variables).forEach((key) => {
|
|
38
|
-
result = result.replaceAll(`{{${key}}}`, variables[key]);
|
|
39
|
-
});
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function generate(templateName, featureName, variables) {
|
|
44
|
-
const templateDir = path.join(projectRoot, ".sdrive", "templates", templateName);
|
|
45
|
-
const govDir = path.join(projectRoot, ".sdrive", "governance", featureName);
|
|
46
|
-
|
|
47
|
-
if (!fs.existsSync(templateDir)) {
|
|
48
|
-
console.error(`❌ Template "${templateName}" not found.`);
|
|
49
|
-
listTemplates();
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
fs.mkdirSync(govDir, { recursive: true });
|
|
54
|
-
|
|
55
|
-
const files = ["spec.json", "plan.json", "tasks.json"];
|
|
56
|
-
|
|
57
|
-
files.forEach((file) => {
|
|
58
|
-
const templateFile = path.join(templateDir, file);
|
|
59
|
-
|
|
60
|
-
if (!fs.existsSync(templateFile)) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let content = loadTemplate(templateName, file);
|
|
65
|
-
content = applyVariables(content, {
|
|
66
|
-
featureName,
|
|
67
|
-
...variables
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
fs.writeFileSync(path.join(govDir, file), content);
|
|
71
|
-
console.log(`✅ Generated: ${featureName}/${file}`);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
console.log(`\n🎉 Feature "${featureName}" generated from "${templateName}" template.`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const templateName = process.argv[2];
|
|
78
|
-
const featureName = process.argv[3];
|
|
79
|
-
const problemStatement = process.argv[4] || "";
|
|
80
|
-
const proposedSolution = process.argv[5] || "";
|
|
81
|
-
const targetAudience = process.argv[6] || "";
|
|
82
|
-
const approach = process.argv[7] || "";
|
|
83
|
-
const flowDescription = process.argv[8] || "";
|
|
84
|
-
|
|
85
|
-
if (!templateName || !featureName) {
|
|
86
|
-
console.error("❌ Usage: node generate-from-template.js <template> <feature> [problem] [solution] [audience] [approach] [flow]");
|
|
87
|
-
listTemplates();
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
generate(templateName, featureName, {
|
|
92
|
-
title: featureName,
|
|
93
|
-
problemStatement,
|
|
94
|
-
proposedSolution,
|
|
95
|
-
targetAudience,
|
|
96
|
-
approach,
|
|
97
|
-
flowDescription
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const projectRoot = process.cwd();
|
|
5
|
+
|
|
6
|
+
function loadRegistry() {
|
|
7
|
+
const registryPath = path.join(projectRoot, ".sdrive", "templates", "registry.json");
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(registryPath)) {
|
|
10
|
+
console.error("❌ registry.json not found.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return JSON.parse(fs.readFileSync(registryPath, "utf8")).templates;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function templateExists(templateName) {
|
|
18
|
+
const registry = loadRegistry();
|
|
19
|
+
return registry.some((t) => t.name === templateName || t.path === templateName);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function listTemplates() {
|
|
23
|
+
const registry = loadRegistry();
|
|
24
|
+
console.log("\nAvailable SpecDrive Templates\n");
|
|
25
|
+
registry.forEach((t) => {
|
|
26
|
+
console.log(` ${t.name} — ${t.description}`);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function loadTemplate(templateName, fileName) {
|
|
31
|
+
const templatePath = path.join(projectRoot, ".sdrive", "templates", templateName, fileName);
|
|
32
|
+
return fs.readFileSync(templatePath, "utf8");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function applyVariables(content, variables) {
|
|
36
|
+
let result = content;
|
|
37
|
+
Object.keys(variables).forEach((key) => {
|
|
38
|
+
result = result.replaceAll(`{{${key}}}`, variables[key]);
|
|
39
|
+
});
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function generate(templateName, featureName, variables) {
|
|
44
|
+
const templateDir = path.join(projectRoot, ".sdrive", "templates", templateName);
|
|
45
|
+
const govDir = path.join(projectRoot, ".sdrive", "governance", featureName);
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(templateDir)) {
|
|
48
|
+
console.error(`❌ Template "${templateName}" not found.`);
|
|
49
|
+
listTemplates();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fs.mkdirSync(govDir, { recursive: true });
|
|
54
|
+
|
|
55
|
+
const files = ["spec.json", "plan.json", "tasks.json"];
|
|
56
|
+
|
|
57
|
+
files.forEach((file) => {
|
|
58
|
+
const templateFile = path.join(templateDir, file);
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(templateFile)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let content = loadTemplate(templateName, file);
|
|
65
|
+
content = applyVariables(content, {
|
|
66
|
+
featureName,
|
|
67
|
+
...variables
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(path.join(govDir, file), content);
|
|
71
|
+
console.log(`✅ Generated: ${featureName}/${file}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log(`\n🎉 Feature "${featureName}" generated from "${templateName}" template.`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const templateName = process.argv[2];
|
|
78
|
+
const featureName = process.argv[3];
|
|
79
|
+
const problemStatement = process.argv[4] || "";
|
|
80
|
+
const proposedSolution = process.argv[5] || "";
|
|
81
|
+
const targetAudience = process.argv[6] || "";
|
|
82
|
+
const approach = process.argv[7] || "";
|
|
83
|
+
const flowDescription = process.argv[8] || "";
|
|
84
|
+
|
|
85
|
+
if (!templateName || !featureName) {
|
|
86
|
+
console.error("❌ Usage: node generate-from-template.js <template> <feature> [problem] [solution] [audience] [approach] [flow]");
|
|
87
|
+
listTemplates();
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
generate(templateName, featureName, {
|
|
92
|
+
title: featureName,
|
|
93
|
+
problemStatement,
|
|
94
|
+
proposedSolution,
|
|
95
|
+
targetAudience,
|
|
96
|
+
approach,
|
|
97
|
+
flowDescription
|
|
98
98
|
});
|
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const projectRoot = process.cwd();
|
|
7
|
-
|
|
8
|
-
function loadPlan(featureName) {
|
|
9
|
-
const planPath = path.join(projectRoot, ".sdrive", "governance", featureName, "plan.json");
|
|
10
|
-
|
|
11
|
-
if (!fs.existsSync(planPath)) {
|
|
12
|
-
console.error(`❌ plan.json not found for feature "${featureName}".`);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return JSON.parse(fs.readFileSync(planPath, "utf8"));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function generateOpenAPI(featureName) {
|
|
20
|
-
const plan = loadPlan(featureName);
|
|
21
|
-
|
|
22
|
-
const openapi = {
|
|
23
|
-
openapi: "3.0.0",
|
|
24
|
-
info: {
|
|
25
|
-
title: `${featureName} API`,
|
|
26
|
-
version: "1.0.0",
|
|
27
|
-
description: `API specification for ${featureName}`
|
|
28
|
-
},
|
|
29
|
-
paths: {}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const contracts = plan.apiContracts || [];
|
|
33
|
-
|
|
34
|
-
contracts.forEach((contract) => {
|
|
35
|
-
const method = contract.method.toLowerCase();
|
|
36
|
-
const apiPath = contract.path;
|
|
37
|
-
|
|
38
|
-
if (!openapi.paths[apiPath]) {
|
|
39
|
-
openapi.paths[apiPath] = {};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
openapi.paths[apiPath][method] = {
|
|
43
|
-
summary: `${contract.method} ${apiPath}`,
|
|
44
|
-
security: contract.authenticationRequired ? [{ bearerAuth: [] }] : [],
|
|
45
|
-
responses: {
|
|
46
|
-
200: {
|
|
47
|
-
description: "Successful response"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
openapi.components = {
|
|
54
|
-
securitySchemes: {
|
|
55
|
-
bearerAuth: {
|
|
56
|
-
type: "http",
|
|
57
|
-
scheme: "bearer"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const outputPath = path.join(projectRoot, ".sdrive", "governance", featureName, "openapi.json");
|
|
63
|
-
fs.writeFileSync(outputPath, JSON.stringify(openapi, null, 2));
|
|
64
|
-
|
|
65
|
-
console.log(`✅ Generated OpenAPI spec.`);
|
|
66
|
-
console.log(` File: ${outputPath}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const featureName = process.argv[2];
|
|
70
|
-
|
|
71
|
-
if (!featureName) {
|
|
72
|
-
console.error("❌ Usage: node generate-openapi.js <feature-name>");
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const projectRoot = process.cwd();
|
|
7
|
+
|
|
8
|
+
function loadPlan(featureName) {
|
|
9
|
+
const planPath = path.join(projectRoot, ".sdrive", "governance", featureName, "plan.json");
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(planPath)) {
|
|
12
|
+
console.error(`❌ plan.json not found for feature "${featureName}".`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return JSON.parse(fs.readFileSync(planPath, "utf8"));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function generateOpenAPI(featureName) {
|
|
20
|
+
const plan = loadPlan(featureName);
|
|
21
|
+
|
|
22
|
+
const openapi = {
|
|
23
|
+
openapi: "3.0.0",
|
|
24
|
+
info: {
|
|
25
|
+
title: `${featureName} API`,
|
|
26
|
+
version: "1.0.0",
|
|
27
|
+
description: `API specification for ${featureName}`
|
|
28
|
+
},
|
|
29
|
+
paths: {}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const contracts = plan.apiContracts || [];
|
|
33
|
+
|
|
34
|
+
contracts.forEach((contract) => {
|
|
35
|
+
const method = contract.method.toLowerCase();
|
|
36
|
+
const apiPath = contract.path;
|
|
37
|
+
|
|
38
|
+
if (!openapi.paths[apiPath]) {
|
|
39
|
+
openapi.paths[apiPath] = {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
openapi.paths[apiPath][method] = {
|
|
43
|
+
summary: `${contract.method} ${apiPath}`,
|
|
44
|
+
security: contract.authenticationRequired ? [{ bearerAuth: [] }] : [],
|
|
45
|
+
responses: {
|
|
46
|
+
200: {
|
|
47
|
+
description: "Successful response"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
openapi.components = {
|
|
54
|
+
securitySchemes: {
|
|
55
|
+
bearerAuth: {
|
|
56
|
+
type: "http",
|
|
57
|
+
scheme: "bearer"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const outputPath = path.join(projectRoot, ".sdrive", "governance", featureName, "openapi.json");
|
|
63
|
+
fs.writeFileSync(outputPath, JSON.stringify(openapi, null, 2));
|
|
64
|
+
|
|
65
|
+
console.log(`✅ Generated OpenAPI spec.`);
|
|
66
|
+
console.log(` File: ${outputPath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const featureName = process.argv[2];
|
|
70
|
+
|
|
71
|
+
if (!featureName) {
|
|
72
|
+
console.error("❌ Usage: node generate-openapi.js <feature-name>");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
76
|
generateOpenAPI(featureName);
|