@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.
@@ -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.”