@deveonc/spec-to-code 1.0.0-beta.2
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 +123 -0
- package/bin/lib/agent-metadata.js +57 -0
- package/bin/lib/checklist.js +81 -0
- package/bin/lib/install-config.js +26 -0
- package/bin/lib/rules-manager.js +42 -0
- package/bin/scripts/claude/generate-skills.js +111 -0
- package/bin/scripts/opencode/generate-commands.js +97 -0
- package/bin/scripts/opencode/generate-opencode-agents.js +96 -0
- package/bin/scripts/rules/configure-rules.js +170 -0
- package/bin/spec-to-code.js +258 -0
- package/package.json +37 -0
- package/templates/.ai/agents/architect.agent.md +45 -0
- package/templates/.ai/agents/develop.agent.md +88 -0
- package/templates/.ai/agents/planning.agent.md +165 -0
- package/templates/.ai/agents/reviewer.agent.md +86 -0
- package/templates/.ai/rules/angular.rules.md +318 -0
- package/templates/.ai/rules/default.rules.md +33 -0
- package/templates/.ai/rules/nextjs.rules.md +42 -0
- package/templates/.ai/rules/quarkus.rules.md +39 -0
- package/templates/.ai/rules/react.rules.md +36 -0
- package/templates/.ai/rules/springboot.rules.md +134 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
|
+
|
|
6
|
+
import { ensureSelection, promptChecklist } from "../../lib/checklist.js";
|
|
7
|
+
import { applyRules, listRuleFiles } from "../../lib/rules-manager.js";
|
|
8
|
+
|
|
9
|
+
const parseArgs = (args) => {
|
|
10
|
+
const options = {};
|
|
11
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
12
|
+
const value = args[index];
|
|
13
|
+
if (value === "--rules" && args[index + 1]) {
|
|
14
|
+
options.rulesDir = args[index + 1];
|
|
15
|
+
index += 1;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (value === "--out" && args[index + 1]) {
|
|
19
|
+
options.targetDir = args[index + 1];
|
|
20
|
+
index += 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return options;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
|
|
28
|
+
const resolvePath = (value) => path.resolve(process.cwd(), value);
|
|
29
|
+
|
|
30
|
+
const MANDATORY_RULES = ["default.rules.md"];
|
|
31
|
+
|
|
32
|
+
const normalizeSelectedRules = (initialSelected) => {
|
|
33
|
+
if (!initialSelected) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalized = new Set(initialSelected);
|
|
38
|
+
if (normalized.has("general.rules.md")) {
|
|
39
|
+
normalized.add("default.rules.md");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return Array.from(normalized);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const buildItems = (ruleNames, selections) => ruleNames.map((name, index) => ({
|
|
46
|
+
label: name,
|
|
47
|
+
checked: selections ? selections[index] : true,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
const selectionsMatch = (left, right) => {
|
|
51
|
+
if (!left || !right || left.length !== right.length) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const leftSet = new Set(left);
|
|
56
|
+
const rightSet = new Set(right);
|
|
57
|
+
if (leftSet.size !== rightSet.size) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const value of leftSet) {
|
|
62
|
+
if (!rightSet.has(value)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const configureRules = async ({ rulesDir, targetDir, initialSelected, forceApply = false }) => {
|
|
71
|
+
const ruleNames = listRuleFiles(rulesDir);
|
|
72
|
+
if (ruleNames.length === 0) {
|
|
73
|
+
console.warn("⚠️ No rules found to configure.");
|
|
74
|
+
return { copied: [], removed: [], selected: [], changed: false };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const prompt = "Toggle selections with numbers (e.g. 1,2) or press Enter: ";
|
|
78
|
+
const title = "Rules to install:";
|
|
79
|
+
const normalizedSelected = normalizeSelectedRules(initialSelected);
|
|
80
|
+
|
|
81
|
+
const mandatoryRules = ruleNames.filter((name) => MANDATORY_RULES.includes(name));
|
|
82
|
+
const selectableRules = ruleNames.filter((name) => !MANDATORY_RULES.includes(name));
|
|
83
|
+
const missingMandatory = MANDATORY_RULES.filter((name) => !ruleNames.includes(name));
|
|
84
|
+
|
|
85
|
+
if (missingMandatory.length > 0) {
|
|
86
|
+
console.warn(`⚠️ Missing mandatory rules: ${missingMandatory.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const baseSelection = selectableRules.map((name) => {
|
|
90
|
+
if (!normalizedSelected.length) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return normalizedSelected.includes(name);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
let selections = baseSelection;
|
|
97
|
+
if (selectableRules.length > 0 && process.stdin.isTTY && process.stdout.isTTY) {
|
|
98
|
+
selections = await promptChecklist({
|
|
99
|
+
title,
|
|
100
|
+
prompt,
|
|
101
|
+
items: buildItems(selectableRules, selections),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (selectableRules.length > 0) {
|
|
106
|
+
selections = await ensureSelection({
|
|
107
|
+
title,
|
|
108
|
+
prompt,
|
|
109
|
+
items: buildItems(selectableRules, selections),
|
|
110
|
+
selections,
|
|
111
|
+
fallbackSelections: selectableRules.map(() => true),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const selected = [
|
|
116
|
+
...mandatoryRules,
|
|
117
|
+
...selectableRules.filter((_, index) => selections[index]),
|
|
118
|
+
];
|
|
119
|
+
const baseSelected = [
|
|
120
|
+
...mandatoryRules,
|
|
121
|
+
...selectableRules.filter((_, index) => baseSelection[index]),
|
|
122
|
+
];
|
|
123
|
+
const changed = !selectionsMatch(selected, baseSelected);
|
|
124
|
+
const missingInTarget = mandatoryRules.filter((name) =>
|
|
125
|
+
!fs.existsSync(path.join(targetDir, name))
|
|
126
|
+
);
|
|
127
|
+
const shouldApply = forceApply || changed || missingInTarget.length > 0;
|
|
128
|
+
|
|
129
|
+
if (!shouldApply) {
|
|
130
|
+
return { copied: [], removed: [], selected, changed: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const results = applyRules({ sourceDir: rulesDir, targetDir, selected });
|
|
134
|
+
|
|
135
|
+
return { ...results, selected, changed: true };
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const run = async () => {
|
|
139
|
+
const { rulesDir, targetDir } = parseArgs(process.argv.slice(2));
|
|
140
|
+
const defaultRulesDir = path.join(__dirname, "../../../templates/.ai/rules");
|
|
141
|
+
const resolvedRulesDir = resolvePath(rulesDir ?? defaultRulesDir);
|
|
142
|
+
const resolvedTargetDir = resolvePath(targetDir ?? path.join(".ai", "rules"));
|
|
143
|
+
|
|
144
|
+
const { copied, removed, selected } = await configureRules({
|
|
145
|
+
rulesDir: resolvedRulesDir,
|
|
146
|
+
targetDir: resolvedTargetDir,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log(`✅ Rules configured in ${resolvedTargetDir}`);
|
|
150
|
+
if (selected.length > 0) {
|
|
151
|
+
console.log(`✅ Selected: ${selected.join(", ")}`);
|
|
152
|
+
}
|
|
153
|
+
if (removed.length > 0) {
|
|
154
|
+
console.log(`🗑️ Removed: ${removed.join(", ")}`);
|
|
155
|
+
}
|
|
156
|
+
if (copied.length > 0) {
|
|
157
|
+
console.log(`✅ Copied: ${copied.join(", ")}`);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
162
|
+
try {
|
|
163
|
+
await run();
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(error);
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { configureRules };
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
import { ensureSelection, promptChecklist } from "./lib/checklist.js";
|
|
7
|
+
import { readAllAgentMetadata } from "./lib/agent-metadata.js";
|
|
8
|
+
import { readInstallConfig, writeInstallConfig } from "./lib/install-config.js";
|
|
9
|
+
import { configureRules } from "./scripts/rules/configure-rules.js";
|
|
10
|
+
import { generateCommands } from "./scripts/opencode/generate-commands.js";
|
|
11
|
+
import { generateOpencodeAgents } from "./scripts/opencode/generate-opencode-agents.js";
|
|
12
|
+
import { generateSkills } from "./scripts/claude/generate-skills.js";
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
const srcAi = path.join(__dirname, "../templates/.ai");
|
|
17
|
+
const dstAi = path.join(process.cwd(), ".ai");
|
|
18
|
+
const opencodeDir = path.join(process.cwd(), ".opencode");
|
|
19
|
+
const opencodeCommandsDir = path.join(opencodeDir, "commands");
|
|
20
|
+
const claudeDir = path.join(process.cwd(), ".claude");
|
|
21
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
22
|
+
const agentsSrc = path.join(srcAi, "agents");
|
|
23
|
+
|
|
24
|
+
const copied = [];
|
|
25
|
+
const skipped = [];
|
|
26
|
+
const removed = [];
|
|
27
|
+
|
|
28
|
+
const getPackageVersion = () => {
|
|
29
|
+
const packagePath = path.join(__dirname, "../package.json");
|
|
30
|
+
const raw = fs.readFileSync(packagePath, "utf8");
|
|
31
|
+
const packageJson = JSON.parse(raw);
|
|
32
|
+
|
|
33
|
+
return packageJson.version ?? "0.0.0";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const clearAiDirectory = (directory, preserveFile) => {
|
|
37
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
const entryPath = path.join(directory, entry.name);
|
|
40
|
+
if (entryPath === preserveFile) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const removeOpenCodeArtifacts = (opencodeRoot, agentMetadata) => {
|
|
48
|
+
const removedPaths = [];
|
|
49
|
+
for (const agent of agentMetadata) {
|
|
50
|
+
const commandPath = path.join(opencodeRoot, "commands", `${agent.command}.md`);
|
|
51
|
+
const agentPath = path.join(opencodeRoot, "agents", `${agent.name}.md`);
|
|
52
|
+
|
|
53
|
+
if (fs.existsSync(commandPath)) {
|
|
54
|
+
fs.rmSync(commandPath, { force: true });
|
|
55
|
+
removedPaths.push(path.join(".opencode", "commands", `${agent.command}.md`));
|
|
56
|
+
}
|
|
57
|
+
if (fs.existsSync(agentPath)) {
|
|
58
|
+
fs.rmSync(agentPath, { force: true });
|
|
59
|
+
removedPaths.push(path.join(".opencode", "agents", `${agent.name}.md`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return removedPaths;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const removeClaudeArtifacts = (claudeRoot, agentMetadata) => {
|
|
67
|
+
const removedPaths = [];
|
|
68
|
+
for (const agent of agentMetadata) {
|
|
69
|
+
const skillPath = path.join(claudeRoot, "skills", agent.name);
|
|
70
|
+
if (fs.existsSync(skillPath)) {
|
|
71
|
+
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
72
|
+
removedPaths.push(path.join(".claude", "skills", agent.name));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return removedPaths;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const run = async () => {
|
|
80
|
+
const configPath = path.join(dstAi, "config.json");
|
|
81
|
+
const packageVersion = getPackageVersion();
|
|
82
|
+
const existingConfig = readInstallConfig(configPath);
|
|
83
|
+
const hasConfig = Boolean(existingConfig);
|
|
84
|
+
const sameVersion = hasConfig && existingConfig.version === packageVersion;
|
|
85
|
+
const isUpdate = hasConfig && existingConfig.version !== packageVersion;
|
|
86
|
+
|
|
87
|
+
if (fs.existsSync(dstAi)) {
|
|
88
|
+
if (isUpdate) {
|
|
89
|
+
clearAiDirectory(dstAi, configPath);
|
|
90
|
+
} else if (!sameVersion) {
|
|
91
|
+
fs.rmSync(dstAi, { recursive: true, force: true });
|
|
92
|
+
skipped.push(".ai/ (removed)");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!sameVersion) {
|
|
97
|
+
fs.cpSync(srcAi, dstAi, { recursive: true });
|
|
98
|
+
copied.push(".ai/");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const agentMetadata = readAllAgentMetadata(agentsSrc);
|
|
102
|
+
const defaultIntegrations = {
|
|
103
|
+
opencode: existingConfig?.integrations?.opencode ?? fs.existsSync(opencodeDir),
|
|
104
|
+
claude: existingConfig?.integrations?.claude ?? fs.existsSync(claudeDir),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const integrations = [
|
|
108
|
+
{
|
|
109
|
+
key: "opencode",
|
|
110
|
+
label: "Install OpenCode commands (.opencode/commands)",
|
|
111
|
+
checked: defaultIntegrations.opencode,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: "claude",
|
|
115
|
+
label: "Install Claude Code skills (.claude/skills)",
|
|
116
|
+
checked: defaultIntegrations.claude,
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
let selections = integrations.map((item) => item.checked);
|
|
121
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
122
|
+
selections = await promptChecklist({
|
|
123
|
+
title: "Optional integrations:",
|
|
124
|
+
prompt: "Toggle selections with numbers (e.g. 1,2) or press Enter: ",
|
|
125
|
+
items: integrations,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
selections = await ensureSelection({
|
|
129
|
+
title: "Optional integrations:",
|
|
130
|
+
prompt: "Toggle selections with numbers (e.g. 1,2) or press Enter: ",
|
|
131
|
+
items: integrations,
|
|
132
|
+
selections,
|
|
133
|
+
fallbackSelections: [true, false],
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const [installOpenCode, installClaude] = selections;
|
|
137
|
+
const integrationChanged =
|
|
138
|
+
!hasConfig ||
|
|
139
|
+
installOpenCode !== defaultIntegrations.opencode ||
|
|
140
|
+
installClaude !== defaultIntegrations.claude;
|
|
141
|
+
const shouldApplyIntegrations = !sameVersion || integrationChanged;
|
|
142
|
+
|
|
143
|
+
if (shouldApplyIntegrations) {
|
|
144
|
+
if (installOpenCode) {
|
|
145
|
+
if (!fs.existsSync(agentsSrc)) {
|
|
146
|
+
console.warn("⚠️ Skipped: .opencode (agents missing)");
|
|
147
|
+
} else {
|
|
148
|
+
const { created, skipped: commandSkipped } = generateCommands({
|
|
149
|
+
agentsDir: agentsSrc,
|
|
150
|
+
commandsDir: opencodeCommandsDir,
|
|
151
|
+
});
|
|
152
|
+
const { created: agentCreated, skipped: agentSkipped } = generateOpencodeAgents({
|
|
153
|
+
agentsDir: agentsSrc,
|
|
154
|
+
outDir: path.join(opencodeDir, "agents"),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
created.forEach((name) => {
|
|
158
|
+
copied.push(path.join(".opencode", "commands", name));
|
|
159
|
+
});
|
|
160
|
+
commandSkipped.forEach((name) => {
|
|
161
|
+
skipped.push(path.join(".opencode", "commands", name));
|
|
162
|
+
});
|
|
163
|
+
agentCreated.forEach((name) => {
|
|
164
|
+
copied.push(path.join(".opencode", "agents", name));
|
|
165
|
+
});
|
|
166
|
+
agentSkipped.forEach((name) => {
|
|
167
|
+
skipped.push(path.join(".opencode", "agents", name));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
} else if (fs.existsSync(opencodeDir) && agentMetadata.length > 0) {
|
|
171
|
+
removed.push(...removeOpenCodeArtifacts(opencodeDir, agentMetadata));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (installClaude) {
|
|
175
|
+
if (!fs.existsSync(agentsSrc)) {
|
|
176
|
+
console.warn("⚠️ Skipped: .claude/skills (agents missing)");
|
|
177
|
+
} else {
|
|
178
|
+
const { created, skipped: skillSkipped } = generateSkills({
|
|
179
|
+
agentsDir: agentsSrc,
|
|
180
|
+
skillsDir: claudeSkillsDir,
|
|
181
|
+
});
|
|
182
|
+
created.forEach((name) => {
|
|
183
|
+
copied.push(path.join(".claude", "skills", name));
|
|
184
|
+
});
|
|
185
|
+
skillSkipped.forEach((name) => {
|
|
186
|
+
skipped.push(path.join(".claude", "skills", name));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} else if (fs.existsSync(claudeDir) && agentMetadata.length > 0) {
|
|
190
|
+
removed.push(...removeClaudeArtifacts(claudeDir, agentMetadata));
|
|
191
|
+
}
|
|
192
|
+
} else if (fs.existsSync(opencodeDir) || fs.existsSync(claudeDir)) {
|
|
193
|
+
skipped.push("integrations (unchanged)");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { selected: selectedRules, removed: removedRules, copied: copiedRules } =
|
|
197
|
+
await configureRules({
|
|
198
|
+
rulesDir: path.join(srcAi, "rules"),
|
|
199
|
+
targetDir: path.join(dstAi, "rules"),
|
|
200
|
+
initialSelected: existingConfig?.rules,
|
|
201
|
+
forceApply: !sameVersion,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
copiedRules.forEach((rule) => {
|
|
205
|
+
copied.push(path.join(".ai", "rules", rule));
|
|
206
|
+
});
|
|
207
|
+
removedRules.forEach((rule) => {
|
|
208
|
+
removed.push(path.join(".ai", "rules", rule));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const now = new Date().toISOString();
|
|
212
|
+
const installConfig = {
|
|
213
|
+
version: packageVersion,
|
|
214
|
+
installedAt: existingConfig?.installedAt ?? now,
|
|
215
|
+
updatedAt: now,
|
|
216
|
+
integrations: {
|
|
217
|
+
opencode: installOpenCode,
|
|
218
|
+
claude: installClaude,
|
|
219
|
+
},
|
|
220
|
+
rules: selectedRules,
|
|
221
|
+
};
|
|
222
|
+
writeInstallConfig(configPath, installConfig);
|
|
223
|
+
|
|
224
|
+
console.log("✅ Spec-to-Code initialized");
|
|
225
|
+
if (copied.length > 0) {
|
|
226
|
+
console.log(`✅ Copied: ${copied.join(", ")}`);
|
|
227
|
+
}
|
|
228
|
+
if (skipped.length > 0) {
|
|
229
|
+
console.log(`⚠️ Skipped: ${skipped.join(", ")}`);
|
|
230
|
+
}
|
|
231
|
+
if (removed.length > 0) {
|
|
232
|
+
console.log(`🗑️ Removed: ${removed.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
console.log(
|
|
235
|
+
`ℹ️ OpenCode commands: ${installOpenCode ? "installed" : "not installed"}`,
|
|
236
|
+
);
|
|
237
|
+
console.log(
|
|
238
|
+
`ℹ️ Claude Code skills: ${installClaude ? "installed" : "not installed"}`,
|
|
239
|
+
);
|
|
240
|
+
if (installOpenCode && installClaude) {
|
|
241
|
+
console.log(
|
|
242
|
+
"➡️ Open OpenCode and run: /Architect, or open Claude Code and use the architect skill.",
|
|
243
|
+
);
|
|
244
|
+
} else if (installOpenCode) {
|
|
245
|
+
console.log("➡️ Open OpenCode and run: /Architect");
|
|
246
|
+
} else if (installClaude) {
|
|
247
|
+
console.log("➡️ Open Claude Code and use the architect skill.");
|
|
248
|
+
} else {
|
|
249
|
+
console.log("➡️ No integration installed. Re-run to enable OpenCode or Claude Code.");
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
await run();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error(error);
|
|
257
|
+
process.exitCode = 1;
|
|
258
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deveonc/spec-to-code",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "Spec-to-Code workflow bootstrap for OpenCode using agent-based LLM development",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test",
|
|
8
|
+
"test:single": "node --test",
|
|
9
|
+
"release": "semantic-release"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"spec-to-code": "./bin/spec-to-code.js"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"templates"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"opencode",
|
|
23
|
+
"llm",
|
|
24
|
+
"agents",
|
|
25
|
+
"spec-to-code"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
30
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
31
|
+
"@semantic-release/git": "^10.0.1",
|
|
32
|
+
"@semantic-release/github": "^11.0.6",
|
|
33
|
+
"@semantic-release/npm": "^12.0.2",
|
|
34
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
35
|
+
"semantic-release": "^24.2.9"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: Start the Architect specification workflow.
|
|
4
|
+
command: Architect
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a senior Product Strategist, UX Researcher, and Technical Analyst named Architect agent.
|
|
8
|
+
|
|
9
|
+
Your goal is to help the user develop a complete, detailed, developer-ready specification for a new app idea.
|
|
10
|
+
|
|
11
|
+
Working rules:
|
|
12
|
+
1. If docs/specs.md does NOT exist, start the specification process immediately.
|
|
13
|
+
2. If docs/specs.md already exists, review it briefly and ask whether to refine or proceed to planning.
|
|
14
|
+
3. Ask strictly ONE question at a time. Never ask multiple questions in the same turn.
|
|
15
|
+
4. Each question must build directly on the previous answer, refining or expanding the specification.
|
|
16
|
+
5. Follow this structured path of exploration in order:
|
|
17
|
+
- Problem & pain points
|
|
18
|
+
- Target users & segments
|
|
19
|
+
- Core use cases
|
|
20
|
+
- Key features & functional scope
|
|
21
|
+
- User journey & UX flows
|
|
22
|
+
- Technical considerations (architecture, integrations, constraints, stack choices)
|
|
23
|
+
- Data model & edge cases
|
|
24
|
+
- Monetization strategy
|
|
25
|
+
- Risks, limitations, dependencies
|
|
26
|
+
- Final consolidated specification
|
|
27
|
+
6. During technical considerations, ask explicitly for frontend/backend stack choices.
|
|
28
|
+
7. Confirm the chosen stack with the user before moving forward (no assumptions).
|
|
29
|
+
8. Do NOT propose features or assumptions unless you have explicitly asked about them first.
|
|
30
|
+
9. Do NOT skip ahead in the structure — proceed logically, guided by my answers.
|
|
31
|
+
10. If the user asks for new features after specs exist, direct them back to Planning to add tasks before any development.
|
|
32
|
+
11. When exploration is complete, compile everything into docs/specs.md.
|
|
33
|
+
The document must include:
|
|
34
|
+
- Functional requirements
|
|
35
|
+
- Non-functional requirements
|
|
36
|
+
- High-level architecture choices
|
|
37
|
+
- Frontend/backend stack choices (confirmed)
|
|
38
|
+
- Data handling and edge cases
|
|
39
|
+
- Error handling strategy
|
|
40
|
+
- Testing strategy
|
|
41
|
+
12. Once docs/specs.md is created, instruct the user to switch to the Planning agent with command /Planning.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Start by asking the first question:
|
|
45
|
+
“What is the core idea of your app in one or two sentences?”
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: develop
|
|
3
|
+
description: Implement a single task from docs/todo.md.
|
|
4
|
+
command: Develop
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a senior Software Engineer named Develop agent.
|
|
8
|
+
|
|
9
|
+
Your goal is to implement the project incrementally by executing tasks from docs/todo.md and validate them by creating and executing test when possible.
|
|
10
|
+
|
|
11
|
+
You operate ONE task at a time.
|
|
12
|
+
|
|
13
|
+
------------------------------------------------------------
|
|
14
|
+
EXECUTION RULES
|
|
15
|
+
------------------------------------------------------------
|
|
16
|
+
1. If no task is specified, pick the first unchecked task in docs/todo.md and ask for confirmation.
|
|
17
|
+
- Include the task ID and title in the confirmation message.
|
|
18
|
+
- Example: "Next available task is S3: Add user search filters. Proceed?"
|
|
19
|
+
2. If any task is marked IN PROGRESS ([~]), warn the user and ask whether to resume it or start the next unchecked task.
|
|
20
|
+
- Example: "Task S2 is marked in progress. Resume S2 or start S3?"
|
|
21
|
+
3. Implement EXACTLY ONE task from docs/todo.md.
|
|
22
|
+
4. If the user asks for a new feature or change not in docs/todo.md, STOP and redirect them to the Planning workflow to add a new task.
|
|
23
|
+
5. Do NOT modify docs/specs.md or docs/implementation_plan.md.
|
|
24
|
+
6. Follow all rules in rules/*.rules.md.
|
|
25
|
+
7. Preserve existing working code whenever possible.
|
|
26
|
+
8. Integrate new code with existing modules (imports, wiring, configuration).
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
------------------------------------------------------------
|
|
30
|
+
TESTING (MANDATORY)
|
|
31
|
+
------------------------------------------------------------
|
|
32
|
+
7. After implementation, you MUST run the relevant test command(s):
|
|
33
|
+
- Examples: `npm test`, `mvn test`, `gradle test`, etc.
|
|
34
|
+
8. If no tests exist:
|
|
35
|
+
- Create them.
|
|
36
|
+
- Then run the test suite.
|
|
37
|
+
9. If tests fail:
|
|
38
|
+
- Fix the code or tests.
|
|
39
|
+
- Re-run tests until they pass.
|
|
40
|
+
|
|
41
|
+
------------------------------------------------------------
|
|
42
|
+
VALIDATION
|
|
43
|
+
------------------------------------------------------------
|
|
44
|
+
10. Summarize:
|
|
45
|
+
- Code changes
|
|
46
|
+
- Tests added or modified
|
|
47
|
+
- Test command(s) executed
|
|
48
|
+
- Test results (pass/fail)
|
|
49
|
+
|
|
50
|
+
------------------------------------------------------------
|
|
51
|
+
HANDOFF TO REVIEW
|
|
52
|
+
------------------------------------------------------------
|
|
53
|
+
11. Once tests pass:
|
|
54
|
+
- Automatically hand off to the Reviewer agent with /Reviewer.
|
|
55
|
+
- If multiple tasks are in progress, ask which task ID to review.
|
|
56
|
+
- Provide all context needed for review.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
------------------------------------------------------------
|
|
60
|
+
POST-REVIEW BEHAVIOR
|
|
61
|
+
------------------------------------------------------------
|
|
62
|
+
12. If the Reviewer approves:
|
|
63
|
+
- Propose updating docs/todo.md by marking the task as completed.
|
|
64
|
+
- Ask whether to proceed with the next task.
|
|
65
|
+
13. If approved and confirmed, suggest the next unchecked task.
|
|
66
|
+
|
|
67
|
+
------------------------------------------------------------
|
|
68
|
+
TASK LOCKING (MULTI-AGENT SAFETY)
|
|
69
|
+
------------------------------------------------------------
|
|
70
|
+
14. Before starting any task:
|
|
71
|
+
- Check docs/todo.md.
|
|
72
|
+
- If the task is marked [~] or [x], DO NOT start it.
|
|
73
|
+
- If the task is [ ], you must lock it by marking it [~].
|
|
74
|
+
|
|
75
|
+
15. When starting a task:
|
|
76
|
+
- Update docs/todo.md by changing:
|
|
77
|
+
[ ] → [~] for the selected task.
|
|
78
|
+
- Clearly state that the task is now IN PROGRESS.
|
|
79
|
+
|
|
80
|
+
16. If the task cannot be completed (blocked, unclear, failing tests):
|
|
81
|
+
- Keep the task marked as [~].
|
|
82
|
+
- Explain why it is blocked.
|
|
83
|
+
|
|
84
|
+
17. Only after Reviewer approval:
|
|
85
|
+
- Propose updating the task from [~] → [x].
|
|
86
|
+
|
|
87
|
+
Default execution format:
|
|
88
|
+
“Implement task {{task_id}} from docs/todo.md.”
|