spets 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +502 -459
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -51,14 +51,319 @@ import { dirname as dirname4, join as join13 } from "path";
|
|
|
51
51
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
52
52
|
|
|
53
53
|
// src/commands/init.ts
|
|
54
|
-
import { existsSync
|
|
55
|
-
import { join
|
|
54
|
+
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync } from "fs";
|
|
55
|
+
import { join, dirname } from "path";
|
|
56
56
|
import { fileURLToPath } from "url";
|
|
57
57
|
import { select } from "@inquirer/prompts";
|
|
58
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
59
|
+
async function initCommand(options) {
|
|
60
|
+
const cwd = process.cwd();
|
|
61
|
+
const spetsDir = getSpetsDir(cwd);
|
|
62
|
+
if (spetsExists(cwd) && !options.force) {
|
|
63
|
+
console.error("\u274C Spets already initialized. Use --force to overwrite.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
if (options.interactive) {
|
|
67
|
+
await runInteractiveSetup(cwd, spetsDir, options);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
mkdirSync(spetsDir, { recursive: true });
|
|
71
|
+
mkdirSync(join(spetsDir, "steps"), { recursive: true });
|
|
72
|
+
mkdirSync(join(spetsDir, "outputs"), { recursive: true });
|
|
73
|
+
mkdirSync(join(spetsDir, "hooks"), { recursive: true });
|
|
74
|
+
mkdirSync(join(spetsDir, "knowledge"), { recursive: true });
|
|
75
|
+
const knowledgeGuideTemplate = join(__dirname, "..", "templates", "knowledge", "guide.md");
|
|
76
|
+
if (existsSync(knowledgeGuideTemplate)) {
|
|
77
|
+
const guideDest = join(spetsDir, "knowledge", "guide.md");
|
|
78
|
+
if (!existsSync(guideDest)) {
|
|
79
|
+
cpSync(knowledgeGuideTemplate, guideDest);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const configPath = join(spetsDir, "config.yml");
|
|
83
|
+
if (!existsSync(configPath)) {
|
|
84
|
+
writeFileSync(configPath, getDefaultConfig());
|
|
85
|
+
}
|
|
86
|
+
createDefaultSteps(spetsDir, options.force);
|
|
87
|
+
printEnhancedInitOutput(options);
|
|
88
|
+
}
|
|
89
|
+
function printEnhancedInitOutput(options) {
|
|
90
|
+
console.log("");
|
|
91
|
+
console.log("\u2501".repeat(60));
|
|
92
|
+
console.log("\u2705 Spets initialized successfully!");
|
|
93
|
+
console.log("\u2501".repeat(60));
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log("\u{1F4C1} Created .spets/ \u2014 Your workflow configuration folder");
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log(" \u{1F4C4} config.yml");
|
|
98
|
+
console.log(" \u21B3 Main configuration file. Start here!");
|
|
99
|
+
console.log(" \u21B3 Configure workflow steps, output paths, hooks, and more");
|
|
100
|
+
console.log(" \u21B3 Each option is documented with comments");
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(" \u{1F4C2} steps/");
|
|
103
|
+
console.log(" \u21B3 01-plan/template.md \u2014 Template for planning documents");
|
|
104
|
+
console.log(" \u21B3 02-implement/template.md \u2014 Template for implementation docs");
|
|
105
|
+
console.log(" \u21B3 Edit these to customize AI output format");
|
|
106
|
+
console.log(" \u21B3 Add/remove steps by editing config.yml AND creating folders");
|
|
107
|
+
console.log("");
|
|
108
|
+
console.log(" \u{1F4C2} hooks/");
|
|
109
|
+
console.log(" \u21B3 Hooks run at workflow events (preStep, postStep, onApprove...)");
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log(" \u{1F4C2} knowledge/");
|
|
112
|
+
console.log(" \u21B3 guide.md \u2014 How to capture learnings from workflows");
|
|
113
|
+
console.log(" \u21B3 Add .md files here to teach future workflows");
|
|
114
|
+
console.log("");
|
|
115
|
+
console.log(" \u{1F4C2} outputs/");
|
|
116
|
+
console.log(" \u21B3 Where generated documents are saved (don't edit)");
|
|
117
|
+
console.log("");
|
|
118
|
+
console.log("\u2501".repeat(60));
|
|
119
|
+
console.log("\u{1F680} Next Steps");
|
|
120
|
+
console.log("\u2501".repeat(60));
|
|
121
|
+
console.log("");
|
|
122
|
+
console.log(" 1. Open .spets/config.yml and review the options");
|
|
123
|
+
console.log(" (Everything is documented with comments!)");
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(" 2. Optionally customize templates in .spets/steps/");
|
|
126
|
+
console.log(" (These control what the AI outputs look like)");
|
|
127
|
+
console.log("");
|
|
128
|
+
console.log(" 3. Install a plugin: spets plugin install claude");
|
|
129
|
+
console.log(" (Also available: codex, gemini)");
|
|
130
|
+
console.log("");
|
|
131
|
+
console.log('\u{1F4A1} Tip: Run "spets --help" to see all available commands');
|
|
132
|
+
console.log("");
|
|
133
|
+
}
|
|
134
|
+
function getDefaultConfig(agent = "claude") {
|
|
135
|
+
return `# \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
136
|
+
# \u2551 SPETS CONFIGURATION \u2551
|
|
137
|
+
# \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
138
|
+
# \u2551 This file controls your Spec-Driven Development workflow. \u2551
|
|
139
|
+
# \u2551 Each section is documented - read the comments! \u2551
|
|
140
|
+
# \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
141
|
+
|
|
142
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
143
|
+
# WORKFLOW STEPS
|
|
144
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
145
|
+
# Define the steps in your workflow. Each step:
|
|
146
|
+
# 1. Has a folder in .spets/steps/<step-name>/
|
|
147
|
+
# 2. Contains a template.md that controls AI output format
|
|
148
|
+
# 3. Runs through 5 phases: Explore \u2192 Clarify \u2192 Execute \u2192 Verify \u2192 Review
|
|
149
|
+
#
|
|
150
|
+
# To add a new step:
|
|
151
|
+
# 1. Add the name here (e.g., "03-test")
|
|
152
|
+
# 2. Create folder: .spets/steps/03-test/
|
|
153
|
+
# 3. Create template: .spets/steps/03-test/template.md
|
|
154
|
+
#
|
|
155
|
+
# To remove a step: Just delete it from this list
|
|
156
|
+
# To reorder: Change the order here
|
|
157
|
+
|
|
158
|
+
steps:
|
|
159
|
+
- 01-plan # Planning phase - analyzes codebase, creates implementation plan
|
|
160
|
+
- 02-implement # Implementation phase - executes the plan, writes code
|
|
161
|
+
|
|
162
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
163
|
+
# AI AGENT
|
|
164
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
165
|
+
# Which AI CLI to use for generating documents.
|
|
166
|
+
# Options: claude, codex, gemini
|
|
167
|
+
|
|
168
|
+
agent: ${agent}
|
|
169
|
+
|
|
170
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
171
|
+
# OUTPUT CONFIGURATION
|
|
172
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
173
|
+
# Where generated documents are saved.
|
|
174
|
+
# Each workflow creates a folder: <path>/<task-id>/
|
|
175
|
+
|
|
176
|
+
output:
|
|
177
|
+
path: .spets/outputs # Relative to project root, or absolute path
|
|
178
|
+
|
|
179
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
180
|
+
# KNOWLEDGE BASE
|
|
181
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
182
|
+
# The knowledge base captures learnings from completed workflows.
|
|
183
|
+
# Add .md files to .spets/knowledge/ to teach future workflows.
|
|
184
|
+
#
|
|
185
|
+
# enabled: Save knowledge suggestions after each workflow
|
|
186
|
+
# inject: Include knowledge files in the explore phase prompt
|
|
187
|
+
|
|
188
|
+
knowledge:
|
|
189
|
+
enabled: true # Prompt to save knowledge after workflows
|
|
190
|
+
inject: true # Use saved knowledge in future workflows
|
|
191
|
+
|
|
192
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
193
|
+
# AGENT TEAMS (optional, Claude Code only)
|
|
194
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
195
|
+
# Requires: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
|
|
196
|
+
# When enabled, explore phase uses parallel teammates for analysis
|
|
197
|
+
# and verify phase uses an independent reviewer agent.
|
|
198
|
+
#
|
|
199
|
+
# team:
|
|
200
|
+
# enabled: true
|
|
201
|
+
|
|
202
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
203
|
+
# HOOKS (optional)
|
|
204
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
205
|
+
# Shell scripts that run at specific workflow events.
|
|
206
|
+
# Paths are relative to project root.
|
|
207
|
+
#
|
|
208
|
+
# Available hooks:
|
|
209
|
+
# preStep - Before each step starts
|
|
210
|
+
# postStep - After each step completes
|
|
211
|
+
# onApprove - When a document is approved
|
|
212
|
+
# onReject - When a workflow is rejected/stopped
|
|
213
|
+
# onComplete - When the entire workflow finishes
|
|
214
|
+
#
|
|
215
|
+
# Environment variables available in hooks:
|
|
216
|
+
# SPETS_TASK_ID, SPETS_STEP_NAME, SPETS_STEP_INDEX,
|
|
217
|
+
# SPETS_OUTPUT_PATH, SPETS_BRANCH, SPETS_CWD
|
|
218
|
+
#
|
|
219
|
+
# hooks:
|
|
220
|
+
# preStep: "./hooks/pre-step.sh"
|
|
221
|
+
# postStep: "./hooks/post-step.sh"
|
|
222
|
+
# onApprove: "./hooks/on-approve.sh"
|
|
223
|
+
# onReject: "./hooks/on-reject.sh"
|
|
224
|
+
# onComplete: "./hooks/on-complete.sh"
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
async function runInteractiveSetup(cwd, spetsDir, options) {
|
|
228
|
+
console.log("");
|
|
229
|
+
console.log("\u2501".repeat(60));
|
|
230
|
+
console.log("\u{1F9D9} Spets Interactive Setup");
|
|
231
|
+
console.log("\u2501".repeat(60));
|
|
232
|
+
console.log("");
|
|
233
|
+
console.log("Let's configure spets for your project.");
|
|
234
|
+
console.log("");
|
|
235
|
+
const agent = await select({
|
|
236
|
+
message: "Which AI CLI do you want to use?",
|
|
237
|
+
choices: [
|
|
238
|
+
{ value: "claude", name: "Claude (Anthropic) \u2014 Recommended" },
|
|
239
|
+
{ value: "codex", name: "Codex (OpenAI)" },
|
|
240
|
+
{ value: "gemini", name: "Gemini (Google)" }
|
|
241
|
+
]
|
|
242
|
+
});
|
|
243
|
+
mkdirSync(spetsDir, { recursive: true });
|
|
244
|
+
mkdirSync(join(spetsDir, "steps"), { recursive: true });
|
|
245
|
+
mkdirSync(join(spetsDir, "outputs"), { recursive: true });
|
|
246
|
+
mkdirSync(join(spetsDir, "hooks"), { recursive: true });
|
|
247
|
+
mkdirSync(join(spetsDir, "knowledge"), { recursive: true });
|
|
248
|
+
const knowledgeGuideTemplate = join(__dirname, "..", "templates", "knowledge", "guide.md");
|
|
249
|
+
const guideDest = join(spetsDir, "knowledge", "guide.md");
|
|
250
|
+
if (existsSync(knowledgeGuideTemplate) && !existsSync(guideDest)) {
|
|
251
|
+
cpSync(knowledgeGuideTemplate, guideDest);
|
|
252
|
+
}
|
|
253
|
+
const configPath = join(spetsDir, "config.yml");
|
|
254
|
+
if (!existsSync(configPath)) {
|
|
255
|
+
writeFileSync(configPath, getDefaultConfig(agent));
|
|
256
|
+
}
|
|
257
|
+
createDefaultSteps(spetsDir, options.force);
|
|
258
|
+
printEnhancedInitOutput(options);
|
|
259
|
+
}
|
|
260
|
+
function createDefaultSteps(spetsDir, force) {
|
|
261
|
+
const planDir = join(spetsDir, "steps", "01-plan");
|
|
262
|
+
mkdirSync(planDir, { recursive: true });
|
|
263
|
+
const planTemplatePath = join(planDir, "template.md");
|
|
264
|
+
if (force || !existsSync(planTemplatePath)) {
|
|
265
|
+
writeFileSync(planTemplatePath, getPlanTemplate());
|
|
266
|
+
}
|
|
267
|
+
const implementDir = join(spetsDir, "steps", "02-implement");
|
|
268
|
+
mkdirSync(implementDir, { recursive: true });
|
|
269
|
+
const implementTemplatePath = join(implementDir, "template.md");
|
|
270
|
+
if (force || !existsSync(implementTemplatePath)) {
|
|
271
|
+
writeFileSync(implementTemplatePath, getImplementTemplate());
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function getPlanTemplate() {
|
|
275
|
+
const fullTemplate = readFileSync(join(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
|
|
276
|
+
return fullTemplate;
|
|
277
|
+
}
|
|
278
|
+
function getImplementTemplate() {
|
|
279
|
+
const fullTemplate = readFileSync(join(__dirname, "..", "templates", "steps", "02-implement", "template.md"), "utf-8");
|
|
280
|
+
return fullTemplate;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/commands/status.ts
|
|
284
|
+
async function statusCommand(options) {
|
|
285
|
+
const cwd = process.cwd();
|
|
286
|
+
if (!spetsExists(cwd)) {
|
|
287
|
+
console.error('Spets not initialized. Run "spets init" first.');
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
const config = loadConfig(cwd);
|
|
291
|
+
if (options.task) {
|
|
292
|
+
showTaskStatus(options.task, config, cwd);
|
|
293
|
+
} else {
|
|
294
|
+
showAllTasks(config, cwd);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function showTaskStatus(taskId, config, cwd) {
|
|
298
|
+
const state = getWorkflowState(taskId, config, cwd);
|
|
299
|
+
const meta = loadTaskMetadata(taskId, cwd);
|
|
300
|
+
if (!state) {
|
|
301
|
+
console.error(`Task '${taskId}' not found.`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
console.log(`Task: ${taskId}`);
|
|
305
|
+
console.log(`Query: ${meta?.userQuery || state.userQuery || "(unknown)"}`);
|
|
306
|
+
console.log(`Status: ${formatStatus(state.status)}`);
|
|
307
|
+
console.log(`Current Step: ${state.currentStepName} (${state.currentStepIndex + 1}/${config.steps.length})`);
|
|
308
|
+
console.log("");
|
|
309
|
+
console.log("Steps:");
|
|
310
|
+
for (let i = 0; i < config.steps.length; i++) {
|
|
311
|
+
const stepName = config.steps[i];
|
|
312
|
+
const output = state.outputs.get(stepName);
|
|
313
|
+
const isCurrent = i === state.currentStepIndex;
|
|
314
|
+
const marker = isCurrent ? "\u2192" : " ";
|
|
315
|
+
const status = output ? formatDocStatus(output.status) : "\u25CB";
|
|
316
|
+
console.log(` ${marker} ${i + 1}. ${stepName} ${status}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function showAllTasks(config, cwd) {
|
|
320
|
+
const tasks = listTasks(cwd);
|
|
321
|
+
if (tasks.length === 0) {
|
|
322
|
+
console.log("No tasks found.");
|
|
323
|
+
console.log("");
|
|
324
|
+
console.log('Start a new task with: spets orchestrate init "your task description"');
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
console.log("Tasks:");
|
|
328
|
+
console.log("");
|
|
329
|
+
for (const taskId of tasks.slice(0, 10)) {
|
|
330
|
+
const state = getWorkflowState(taskId, config, cwd);
|
|
331
|
+
const meta = loadTaskMetadata(taskId, cwd);
|
|
332
|
+
if (!state) continue;
|
|
333
|
+
const query = meta?.userQuery || state.userQuery || "(no query)";
|
|
334
|
+
const truncatedQuery = query.length > 50 ? query.substring(0, 47) + "..." : query;
|
|
335
|
+
const progress = `${state.currentStepIndex + 1}/${config.steps.length}`;
|
|
336
|
+
console.log(` ${taskId} ${formatStatus(state.status)} [${progress}]`);
|
|
337
|
+
console.log(` ${truncatedQuery}`);
|
|
338
|
+
console.log("");
|
|
339
|
+
}
|
|
340
|
+
if (tasks.length > 10) {
|
|
341
|
+
console.log(` ... and ${tasks.length - 10} more tasks`);
|
|
342
|
+
}
|
|
343
|
+
console.log("");
|
|
344
|
+
console.log('Use "spets status -t <taskId>" for details');
|
|
345
|
+
}
|
|
346
|
+
function formatStatus(status) {
|
|
347
|
+
const icons = {
|
|
348
|
+
in_progress: "\u{1F504} in_progress",
|
|
349
|
+
completed: "\u2705 completed",
|
|
350
|
+
paused: "\u23F8\uFE0F paused",
|
|
351
|
+
rejected: "\u274C rejected"
|
|
352
|
+
};
|
|
353
|
+
return icons[status] || status;
|
|
354
|
+
}
|
|
355
|
+
function formatDocStatus(status) {
|
|
356
|
+
const icons = {
|
|
357
|
+
draft: "\u25D0",
|
|
358
|
+
approved: "\u25CF",
|
|
359
|
+
rejected: "\u2717"
|
|
360
|
+
};
|
|
361
|
+
return icons[status] || "\u25CB";
|
|
362
|
+
}
|
|
58
363
|
|
|
59
364
|
// src/commands/plugin.ts
|
|
60
|
-
import { existsSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "fs";
|
|
61
|
-
import { join } from "path";
|
|
365
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync, readdirSync } from "fs";
|
|
366
|
+
import { join as join2 } from "path";
|
|
62
367
|
import { homedir } from "os";
|
|
63
368
|
async function pluginCommand(action, name) {
|
|
64
369
|
switch (action) {
|
|
@@ -102,10 +407,10 @@ async function installPlugin(name) {
|
|
|
102
407
|
installer();
|
|
103
408
|
}
|
|
104
409
|
function installClaudePlugin() {
|
|
105
|
-
const commandsDir =
|
|
106
|
-
|
|
107
|
-
const skillPath =
|
|
108
|
-
|
|
410
|
+
const commandsDir = join2(process.cwd(), ".claude", "commands");
|
|
411
|
+
mkdirSync2(commandsDir, { recursive: true });
|
|
412
|
+
const skillPath = join2(commandsDir, "spets.md");
|
|
413
|
+
writeFileSync2(skillPath, getClaudeSkillContent());
|
|
109
414
|
console.log("Installed Claude Code plugin.");
|
|
110
415
|
console.log(`Location: ${skillPath}`);
|
|
111
416
|
console.log("");
|
|
@@ -116,10 +421,10 @@ function installClaudePlugin() {
|
|
|
116
421
|
console.log("No additional Claude processes are spawned.");
|
|
117
422
|
}
|
|
118
423
|
function installCodexPlugin() {
|
|
119
|
-
const spetsDir =
|
|
120
|
-
|
|
121
|
-
const skillPath =
|
|
122
|
-
|
|
424
|
+
const spetsDir = join2(process.cwd(), ".codex", "skills", "spets");
|
|
425
|
+
mkdirSync2(spetsDir, { recursive: true });
|
|
426
|
+
const skillPath = join2(spetsDir, "SKILL.md");
|
|
427
|
+
writeFileSync2(skillPath, getCodexSkillContent());
|
|
123
428
|
console.log("Installed Codex CLI plugin.");
|
|
124
429
|
console.log(`Location: ${skillPath}`);
|
|
125
430
|
console.log("");
|
|
@@ -130,10 +435,10 @@ function installCodexPlugin() {
|
|
|
130
435
|
console.log("No additional Codex processes are spawned.");
|
|
131
436
|
}
|
|
132
437
|
function installGeminiPlugin() {
|
|
133
|
-
const spetsDir =
|
|
134
|
-
|
|
135
|
-
const skillPath =
|
|
136
|
-
|
|
438
|
+
const spetsDir = join2(process.cwd(), ".gemini", "skills", "spets");
|
|
439
|
+
mkdirSync2(spetsDir, { recursive: true });
|
|
440
|
+
const skillPath = join2(spetsDir, "SKILL.md");
|
|
441
|
+
writeFileSync2(skillPath, getGeminiSkillContent());
|
|
137
442
|
console.log("Installed Gemini CLI plugin.");
|
|
138
443
|
console.log(`Location: ${skillPath}`);
|
|
139
444
|
console.log("");
|
|
@@ -145,19 +450,19 @@ function installGeminiPlugin() {
|
|
|
145
450
|
}
|
|
146
451
|
async function uninstallPlugin(name) {
|
|
147
452
|
if (name === "claude") {
|
|
148
|
-
const skillPath =
|
|
149
|
-
const globalSkillPath =
|
|
150
|
-
const legacySkillPath =
|
|
453
|
+
const skillPath = join2(process.cwd(), ".claude", "commands", "spets.md");
|
|
454
|
+
const globalSkillPath = join2(homedir(), ".claude", "commands", "spets.md");
|
|
455
|
+
const legacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
|
|
151
456
|
let uninstalled = false;
|
|
152
|
-
if (
|
|
457
|
+
if (existsSync2(skillPath)) {
|
|
153
458
|
rmSync(skillPath);
|
|
154
459
|
uninstalled = true;
|
|
155
460
|
}
|
|
156
|
-
if (
|
|
461
|
+
if (existsSync2(globalSkillPath)) {
|
|
157
462
|
rmSync(globalSkillPath);
|
|
158
463
|
uninstalled = true;
|
|
159
464
|
}
|
|
160
|
-
if (
|
|
465
|
+
if (existsSync2(legacySkillPath)) {
|
|
161
466
|
rmSync(legacySkillPath);
|
|
162
467
|
uninstalled = true;
|
|
163
468
|
}
|
|
@@ -169,15 +474,15 @@ async function uninstallPlugin(name) {
|
|
|
169
474
|
return;
|
|
170
475
|
}
|
|
171
476
|
if (name === "codex") {
|
|
172
|
-
const projectSkillPath =
|
|
173
|
-
const globalSkillPath =
|
|
477
|
+
const projectSkillPath = join2(process.cwd(), ".codex", "skills", "spets", "SKILL.md");
|
|
478
|
+
const globalSkillPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
174
479
|
let uninstalled = false;
|
|
175
480
|
for (const skillPath of [projectSkillPath, globalSkillPath]) {
|
|
176
|
-
if (
|
|
481
|
+
if (existsSync2(skillPath)) {
|
|
177
482
|
rmSync(skillPath);
|
|
178
483
|
try {
|
|
179
|
-
const skillDir =
|
|
180
|
-
const skillsDir =
|
|
484
|
+
const skillDir = join2(skillPath, "..");
|
|
485
|
+
const skillsDir = join2(skillDir, "..");
|
|
181
486
|
rmSync(skillDir, { recursive: true });
|
|
182
487
|
const remaining = readdirSync(skillsDir);
|
|
183
488
|
if (remaining.length === 0) {
|
|
@@ -196,15 +501,15 @@ async function uninstallPlugin(name) {
|
|
|
196
501
|
return;
|
|
197
502
|
}
|
|
198
503
|
if (name === "gemini") {
|
|
199
|
-
const projectSkillPath =
|
|
200
|
-
const legacyCommandPath =
|
|
201
|
-
const legacyGlobalSkillPath =
|
|
504
|
+
const projectSkillPath = join2(process.cwd(), ".gemini", "skills", "spets", "SKILL.md");
|
|
505
|
+
const legacyCommandPath = join2(process.cwd(), ".gemini", "commands", "spets.md");
|
|
506
|
+
const legacyGlobalSkillPath = join2(homedir(), ".gemini", "skills", "spets", "SKILL.md");
|
|
202
507
|
let uninstalled = false;
|
|
203
|
-
if (
|
|
508
|
+
if (existsSync2(projectSkillPath)) {
|
|
204
509
|
rmSync(projectSkillPath);
|
|
205
510
|
try {
|
|
206
|
-
const skillDir =
|
|
207
|
-
const skillsDir =
|
|
511
|
+
const skillDir = join2(projectSkillPath, "..");
|
|
512
|
+
const skillsDir = join2(skillDir, "..");
|
|
208
513
|
rmSync(skillDir, { recursive: true });
|
|
209
514
|
const remaining = readdirSync(skillsDir);
|
|
210
515
|
if (remaining.length === 0) {
|
|
@@ -214,14 +519,14 @@ async function uninstallPlugin(name) {
|
|
|
214
519
|
}
|
|
215
520
|
uninstalled = true;
|
|
216
521
|
}
|
|
217
|
-
if (
|
|
522
|
+
if (existsSync2(legacyCommandPath)) {
|
|
218
523
|
rmSync(legacyCommandPath);
|
|
219
524
|
uninstalled = true;
|
|
220
525
|
}
|
|
221
|
-
if (
|
|
526
|
+
if (existsSync2(legacyGlobalSkillPath)) {
|
|
222
527
|
rmSync(legacyGlobalSkillPath);
|
|
223
528
|
try {
|
|
224
|
-
const skillDir =
|
|
529
|
+
const skillDir = join2(legacyGlobalSkillPath, "..");
|
|
225
530
|
const remaining = readdirSync(skillDir);
|
|
226
531
|
if (remaining.length === 0) {
|
|
227
532
|
rmSync(skillDir, { recursive: true });
|
|
@@ -247,22 +552,22 @@ async function listPlugins() {
|
|
|
247
552
|
console.log(" codex - Codex CLI $spets skill (project-level)");
|
|
248
553
|
console.log(" gemini - Gemini CLI spets skill (project-level)");
|
|
249
554
|
console.log("");
|
|
250
|
-
const claudeProjectPath =
|
|
251
|
-
const claudeGlobalPath =
|
|
252
|
-
const claudeLegacySkillPath =
|
|
253
|
-
const claudeInstalled =
|
|
254
|
-
const codexProjectPath =
|
|
255
|
-
const codexGlobalPath =
|
|
256
|
-
const codexInstalled =
|
|
257
|
-
const codexLocation =
|
|
258
|
-
const geminiProjectPath =
|
|
259
|
-
const geminiLegacyCommandPath =
|
|
260
|
-
const geminiLegacyGlobalPath =
|
|
261
|
-
const geminiInstalled =
|
|
262
|
-
const geminiLocation =
|
|
555
|
+
const claudeProjectPath = join2(process.cwd(), ".claude", "commands", "spets.md");
|
|
556
|
+
const claudeGlobalPath = join2(homedir(), ".claude", "commands", "spets.md");
|
|
557
|
+
const claudeLegacySkillPath = join2(homedir(), ".claude", "commands", "sdd-do.md");
|
|
558
|
+
const claudeInstalled = existsSync2(claudeProjectPath) || existsSync2(claudeGlobalPath) || existsSync2(claudeLegacySkillPath);
|
|
559
|
+
const codexProjectPath = join2(process.cwd(), ".codex", "skills", "spets", "SKILL.md");
|
|
560
|
+
const codexGlobalPath = join2(homedir(), ".codex", "skills", "spets", "SKILL.md");
|
|
561
|
+
const codexInstalled = existsSync2(codexProjectPath) || existsSync2(codexGlobalPath);
|
|
562
|
+
const codexLocation = existsSync2(codexProjectPath) ? "project" : existsSync2(codexGlobalPath) ? "global" : null;
|
|
563
|
+
const geminiProjectPath = join2(process.cwd(), ".gemini", "skills", "spets", "SKILL.md");
|
|
564
|
+
const geminiLegacyCommandPath = join2(process.cwd(), ".gemini", "commands", "spets.md");
|
|
565
|
+
const geminiLegacyGlobalPath = join2(homedir(), ".gemini", "skills", "spets", "SKILL.md");
|
|
566
|
+
const geminiInstalled = existsSync2(geminiProjectPath) || existsSync2(geminiLegacyCommandPath) || existsSync2(geminiLegacyGlobalPath);
|
|
567
|
+
const geminiLocation = existsSync2(geminiProjectPath) ? "project" : existsSync2(geminiLegacyCommandPath) ? "project" : existsSync2(geminiLegacyGlobalPath) ? "global" : null;
|
|
263
568
|
console.log("Installed:");
|
|
264
569
|
const installed = [];
|
|
265
|
-
const claudeLocation =
|
|
570
|
+
const claudeLocation = existsSync2(claudeProjectPath) ? "project" : existsSync2(claudeGlobalPath) ? "global" : "legacy";
|
|
266
571
|
if (claudeInstalled) installed.push(`claude (${claudeLocation})`);
|
|
267
572
|
if (codexInstalled) installed.push(`codex (${codexLocation})`);
|
|
268
573
|
if (geminiInstalled) installed.push(`gemini (${geminiLocation})`);
|
|
@@ -283,11 +588,11 @@ You execute spets workflow commands. Follow orchestrator instructions exactly.
|
|
|
283
588
|
|
|
284
589
|
IF \`$ARGUMENTS\` starts with "resume":
|
|
285
590
|
Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
|
|
286
|
-
RUN \`npx spets orchestrate resume <taskId>\` (omit taskId if none provided)
|
|
591
|
+
RUN \`npx --yes spets orchestrate resume <taskId>\` (omit taskId if none provided)
|
|
287
592
|
ELSE IF \`$ARGUMENTS\` starts with "list":
|
|
288
|
-
RUN \`npx spets orchestrate list\`
|
|
593
|
+
RUN \`npx --yes spets orchestrate list\`
|
|
289
594
|
ELSE:
|
|
290
|
-
RUN \`npx spets orchestrate init "$ARGUMENTS"\`
|
|
595
|
+
RUN \`npx --yes spets orchestrate init "$ARGUMENTS"\`
|
|
291
596
|
|
|
292
597
|
## Loop
|
|
293
598
|
|
|
@@ -325,53 +630,11 @@ You execute spets workflow commands. Follow orchestrator instructions exactly.
|
|
|
325
630
|
|
|
326
631
|
IF \`$ARGUMENTS\` starts with "resume":
|
|
327
632
|
Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
|
|
328
|
-
RUN \`npx spets orchestrate resume <taskId>\` (omit taskId if none provided)
|
|
329
|
-
ELSE IF \`$ARGUMENTS\` starts with "list":
|
|
330
|
-
RUN \`npx spets orchestrate list\`
|
|
331
|
-
ELSE:
|
|
332
|
-
RUN \`npx spets orchestrate init "$ARGUMENTS"\`
|
|
333
|
-
|
|
334
|
-
## Loop
|
|
335
|
-
|
|
336
|
-
1. Parse the JSON response
|
|
337
|
-
2. Read the \`instructions\` field
|
|
338
|
-
3. Follow the instructions exactly
|
|
339
|
-
4. Repeat until the response says to stop
|
|
340
|
-
|
|
341
|
-
## Rules
|
|
342
|
-
|
|
343
|
-
- NEVER skip or modify orchestrator commands
|
|
344
|
-
- NEVER improvise steps \u2014 only do what instructions say
|
|
345
|
-
- Always pass JSON output as a single-quoted string argument
|
|
346
|
-
- Minify JSON when passing as command arguments
|
|
347
|
-
|
|
348
|
-
## Forbidden
|
|
349
|
-
|
|
350
|
-
Planning mode, task tracking tools
|
|
351
|
-
|
|
352
|
-
$ARGUMENTS
|
|
353
|
-
description: Task description for the workflow (or "resume" to list/continue previous workflows)
|
|
354
|
-
`;
|
|
355
|
-
}
|
|
356
|
-
function getGeminiSkillContent() {
|
|
357
|
-
return `---
|
|
358
|
-
name: spets
|
|
359
|
-
description: SDD workflow executor - orchestrator-controlled spec-driven development. Activate when the user wants to run spets, SDD workflows, or spec-driven development tasks.
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
# Spets Executor
|
|
363
|
-
|
|
364
|
-
You execute spets workflow commands. Follow orchestrator instructions exactly.
|
|
365
|
-
|
|
366
|
-
## Startup
|
|
367
|
-
|
|
368
|
-
IF \`$ARGUMENTS\` starts with "resume":
|
|
369
|
-
Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
|
|
370
|
-
RUN \`npx spets orchestrate resume <taskId>\` (omit taskId if none provided)
|
|
633
|
+
RUN \`node dist/index.js orchestrate resume <taskId> || npx --yes spets orchestrate resume <taskId>\` (omit taskId if none provided)
|
|
371
634
|
ELSE IF \`$ARGUMENTS\` starts with "list":
|
|
372
|
-
RUN \`npx spets orchestrate list\`
|
|
635
|
+
RUN \`node dist/index.js orchestrate list || npx --yes spets orchestrate list\`
|
|
373
636
|
ELSE:
|
|
374
|
-
RUN \`npx spets orchestrate init "$ARGUMENTS"\`
|
|
637
|
+
RUN \`node dist/index.js orchestrate init "$ARGUMENTS" || npx --yes spets orchestrate init "$ARGUMENTS"\`
|
|
375
638
|
|
|
376
639
|
## Loop
|
|
377
640
|
|
|
@@ -389,327 +652,53 @@ ELSE:
|
|
|
389
652
|
|
|
390
653
|
## Forbidden
|
|
391
654
|
|
|
392
|
-
Planning mode, task tracking tools
|
|
393
|
-
|
|
394
|
-
$ARGUMENTS
|
|
395
|
-
description: Task description for the workflow (or "resume" to list/continue previous workflows)
|
|
396
|
-
`;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// src/commands/init.ts
|
|
400
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
401
|
-
async function initCommand(options) {
|
|
402
|
-
const cwd = process.cwd();
|
|
403
|
-
const spetsDir = getSpetsDir(cwd);
|
|
404
|
-
if (spetsExists(cwd) && !options.force) {
|
|
405
|
-
console.error("\u274C Spets already initialized. Use --force to overwrite.");
|
|
406
|
-
process.exit(1);
|
|
407
|
-
}
|
|
408
|
-
if (options.interactive) {
|
|
409
|
-
await runInteractiveSetup(cwd, spetsDir, options);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
mkdirSync2(spetsDir, { recursive: true });
|
|
413
|
-
mkdirSync2(join2(spetsDir, "steps"), { recursive: true });
|
|
414
|
-
mkdirSync2(join2(spetsDir, "outputs"), { recursive: true });
|
|
415
|
-
mkdirSync2(join2(spetsDir, "hooks"), { recursive: true });
|
|
416
|
-
mkdirSync2(join2(spetsDir, "knowledge"), { recursive: true });
|
|
417
|
-
const knowledgeGuideTemplate = join2(__dirname, "..", "templates", "knowledge", "guide.md");
|
|
418
|
-
if (existsSync2(knowledgeGuideTemplate)) {
|
|
419
|
-
const guideDest = join2(spetsDir, "knowledge", "guide.md");
|
|
420
|
-
if (!existsSync2(guideDest)) {
|
|
421
|
-
cpSync(knowledgeGuideTemplate, guideDest);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const configPath = join2(spetsDir, "config.yml");
|
|
425
|
-
if (!existsSync2(configPath)) {
|
|
426
|
-
writeFileSync2(configPath, getDefaultConfig());
|
|
427
|
-
}
|
|
428
|
-
createDefaultSteps(spetsDir);
|
|
429
|
-
createClaudeCommand(cwd);
|
|
430
|
-
printEnhancedInitOutput(options);
|
|
431
|
-
}
|
|
432
|
-
function printEnhancedInitOutput(options) {
|
|
433
|
-
console.log("");
|
|
434
|
-
console.log("\u2501".repeat(60));
|
|
435
|
-
console.log("\u2705 Spets initialized successfully!");
|
|
436
|
-
console.log("\u2501".repeat(60));
|
|
437
|
-
console.log("");
|
|
438
|
-
console.log("\u{1F4C1} Created .spets/ \u2014 Your workflow configuration folder");
|
|
439
|
-
console.log("");
|
|
440
|
-
console.log(" \u{1F4C4} config.yml");
|
|
441
|
-
console.log(" \u21B3 Main configuration file. Start here!");
|
|
442
|
-
console.log(" \u21B3 Configure workflow steps, output paths, hooks, and more");
|
|
443
|
-
console.log(" \u21B3 Each option is documented with comments");
|
|
444
|
-
console.log("");
|
|
445
|
-
console.log(" \u{1F4C2} steps/");
|
|
446
|
-
console.log(" \u21B3 01-plan/template.md \u2014 Template for planning documents");
|
|
447
|
-
console.log(" \u21B3 02-implement/template.md \u2014 Template for implementation docs");
|
|
448
|
-
console.log(" \u21B3 Edit these to customize AI output format");
|
|
449
|
-
console.log(" \u21B3 Add/remove steps by editing config.yml AND creating folders");
|
|
450
|
-
console.log("");
|
|
451
|
-
console.log(" \u{1F4C2} hooks/");
|
|
452
|
-
console.log(" \u21B3 Hooks run at workflow events (preStep, postStep, onApprove...)");
|
|
453
|
-
console.log("");
|
|
454
|
-
console.log(" \u{1F4C2} knowledge/");
|
|
455
|
-
console.log(" \u21B3 guide.md \u2014 How to capture learnings from workflows");
|
|
456
|
-
console.log(" \u21B3 Add .md files here to teach future workflows");
|
|
457
|
-
console.log("");
|
|
458
|
-
console.log(" \u{1F4C2} outputs/");
|
|
459
|
-
console.log(" \u21B3 Where generated documents are saved (don't edit)");
|
|
460
|
-
console.log("");
|
|
461
|
-
console.log("\u{1F4C4} .claude/commands/spets.md \u2014 Claude Code integration");
|
|
462
|
-
console.log(" \u21B3 Use /spets in Claude Code to run workflows");
|
|
463
|
-
console.log("");
|
|
464
|
-
console.log("\u2501".repeat(60));
|
|
465
|
-
console.log("\u{1F680} Next Steps");
|
|
466
|
-
console.log("\u2501".repeat(60));
|
|
467
|
-
console.log("");
|
|
468
|
-
console.log(" 1. Open .spets/config.yml and review the options");
|
|
469
|
-
console.log(" (Everything is documented with comments!)");
|
|
470
|
-
console.log("");
|
|
471
|
-
console.log(" 2. Optionally customize templates in .spets/steps/");
|
|
472
|
-
console.log(" (These control what the AI outputs look like)");
|
|
473
|
-
console.log("");
|
|
474
|
-
console.log(" 3. Use /spets in Claude Code to run your first workflow");
|
|
475
|
-
console.log("");
|
|
476
|
-
console.log('\u{1F4A1} Tip: Run "spets --help" to see all available commands');
|
|
477
|
-
console.log("");
|
|
478
|
-
}
|
|
479
|
-
function getDefaultConfig(agent = "claude") {
|
|
480
|
-
return `# \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
481
|
-
# \u2551 SPETS CONFIGURATION \u2551
|
|
482
|
-
# \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
483
|
-
# \u2551 This file controls your Spec-Driven Development workflow. \u2551
|
|
484
|
-
# \u2551 Each section is documented - read the comments! \u2551
|
|
485
|
-
# \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
486
|
-
|
|
487
|
-
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
488
|
-
# WORKFLOW STEPS
|
|
489
|
-
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
490
|
-
# Define the steps in your workflow. Each step:
|
|
491
|
-
# 1. Has a folder in .spets/steps/<step-name>/
|
|
492
|
-
# 2. Contains a template.md that controls AI output format
|
|
493
|
-
# 3. Runs through 5 phases: Explore \u2192 Clarify \u2192 Execute \u2192 Verify \u2192 Review
|
|
494
|
-
#
|
|
495
|
-
# To add a new step:
|
|
496
|
-
# 1. Add the name here (e.g., "03-test")
|
|
497
|
-
# 2. Create folder: .spets/steps/03-test/
|
|
498
|
-
# 3. Create template: .spets/steps/03-test/template.md
|
|
499
|
-
#
|
|
500
|
-
# To remove a step: Just delete it from this list
|
|
501
|
-
# To reorder: Change the order here
|
|
655
|
+
Planning mode, task tracking tools
|
|
502
656
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
657
|
+
$ARGUMENTS
|
|
658
|
+
description: Task description for the workflow (or "resume" to list/continue previous workflows)
|
|
659
|
+
`;
|
|
660
|
+
}
|
|
661
|
+
function getGeminiSkillContent() {
|
|
662
|
+
return `---
|
|
663
|
+
name: spets
|
|
664
|
+
description: SDD workflow executor - orchestrator-controlled spec-driven development. Activate when the user wants to run spets, SDD workflows, or spec-driven development tasks.
|
|
665
|
+
---
|
|
506
666
|
|
|
507
|
-
#
|
|
508
|
-
# AI AGENT
|
|
509
|
-
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
510
|
-
# Which AI CLI to use for generating documents.
|
|
511
|
-
# Options: claude, codex, gemini
|
|
667
|
+
# Spets Executor
|
|
512
668
|
|
|
513
|
-
|
|
669
|
+
You execute spets workflow commands. Follow orchestrator instructions exactly.
|
|
514
670
|
|
|
515
|
-
|
|
516
|
-
# OUTPUT CONFIGURATION
|
|
517
|
-
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
518
|
-
# Where generated documents are saved.
|
|
519
|
-
# Each workflow creates a folder: <path>/<task-id>/
|
|
671
|
+
## Startup
|
|
520
672
|
|
|
521
|
-
|
|
522
|
-
|
|
673
|
+
IF \`$ARGUMENTS\` starts with "resume":
|
|
674
|
+
Extract optional task ID from arguments (e.g. "resume abc-123" or "resume --task abc-123")
|
|
675
|
+
RUN \`npx --yes spets orchestrate resume <taskId>\` (omit taskId if none provided)
|
|
676
|
+
ELSE IF \`$ARGUMENTS\` starts with "list":
|
|
677
|
+
RUN \`npx --yes spets orchestrate list\`
|
|
678
|
+
ELSE:
|
|
679
|
+
RUN \`npx --yes spets orchestrate init "$ARGUMENTS"\`
|
|
523
680
|
|
|
524
|
-
|
|
525
|
-
# KNOWLEDGE BASE
|
|
526
|
-
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
527
|
-
# The knowledge base captures learnings from completed workflows.
|
|
528
|
-
# Add .md files to .spets/knowledge/ to teach future workflows.
|
|
529
|
-
#
|
|
530
|
-
# enabled: Save knowledge suggestions after each workflow
|
|
531
|
-
# inject: Include knowledge files in the explore phase prompt
|
|
681
|
+
## Loop
|
|
532
682
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
683
|
+
1. Parse the JSON response
|
|
684
|
+
2. Read the \`instructions\` field
|
|
685
|
+
3. Follow the instructions exactly
|
|
686
|
+
4. Repeat until the response says to stop
|
|
536
687
|
|
|
537
|
-
|
|
538
|
-
# AGENT TEAMS (optional, Claude Code only)
|
|
539
|
-
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
540
|
-
# Requires: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
|
|
541
|
-
# When enabled, explore phase uses parallel teammates for analysis
|
|
542
|
-
# and verify phase uses an independent reviewer agent.
|
|
543
|
-
#
|
|
544
|
-
# team:
|
|
545
|
-
# enabled: true
|
|
688
|
+
## Rules
|
|
546
689
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
# Paths are relative to project root.
|
|
552
|
-
#
|
|
553
|
-
# Available hooks:
|
|
554
|
-
# preStep - Before each step starts
|
|
555
|
-
# postStep - After each step completes
|
|
556
|
-
# onApprove - When a document is approved
|
|
557
|
-
# onReject - When a workflow is rejected/stopped
|
|
558
|
-
# onComplete - When the entire workflow finishes
|
|
559
|
-
#
|
|
560
|
-
# Environment variables available in hooks:
|
|
561
|
-
# SPETS_TASK_ID, SPETS_STEP_NAME, SPETS_STEP_INDEX,
|
|
562
|
-
# SPETS_OUTPUT_PATH, SPETS_BRANCH, SPETS_CWD
|
|
563
|
-
#
|
|
564
|
-
# hooks:
|
|
565
|
-
# preStep: "./hooks/pre-step.sh"
|
|
566
|
-
# postStep: "./hooks/post-step.sh"
|
|
567
|
-
# onApprove: "./hooks/on-approve.sh"
|
|
568
|
-
# onReject: "./hooks/on-reject.sh"
|
|
569
|
-
# onComplete: "./hooks/on-complete.sh"
|
|
570
|
-
`;
|
|
571
|
-
}
|
|
572
|
-
async function runInteractiveSetup(cwd, spetsDir, options) {
|
|
573
|
-
console.log("");
|
|
574
|
-
console.log("\u2501".repeat(60));
|
|
575
|
-
console.log("\u{1F9D9} Spets Interactive Setup");
|
|
576
|
-
console.log("\u2501".repeat(60));
|
|
577
|
-
console.log("");
|
|
578
|
-
console.log("Let's configure spets for your project.");
|
|
579
|
-
console.log("");
|
|
580
|
-
const agent = await select({
|
|
581
|
-
message: "Which AI CLI do you want to use?",
|
|
582
|
-
choices: [
|
|
583
|
-
{ value: "claude", name: "Claude (Anthropic) \u2014 Recommended" },
|
|
584
|
-
{ value: "codex", name: "Codex (OpenAI)" },
|
|
585
|
-
{ value: "gemini", name: "Gemini (Google)" }
|
|
586
|
-
]
|
|
587
|
-
});
|
|
588
|
-
mkdirSync2(spetsDir, { recursive: true });
|
|
589
|
-
mkdirSync2(join2(spetsDir, "steps"), { recursive: true });
|
|
590
|
-
mkdirSync2(join2(spetsDir, "outputs"), { recursive: true });
|
|
591
|
-
mkdirSync2(join2(spetsDir, "hooks"), { recursive: true });
|
|
592
|
-
mkdirSync2(join2(spetsDir, "knowledge"), { recursive: true });
|
|
593
|
-
const knowledgeGuideTemplate = join2(__dirname, "..", "templates", "knowledge", "guide.md");
|
|
594
|
-
const guideDest = join2(spetsDir, "knowledge", "guide.md");
|
|
595
|
-
if (existsSync2(knowledgeGuideTemplate) && !existsSync2(guideDest)) {
|
|
596
|
-
cpSync(knowledgeGuideTemplate, guideDest);
|
|
597
|
-
}
|
|
598
|
-
const configPath = join2(spetsDir, "config.yml");
|
|
599
|
-
if (!existsSync2(configPath)) {
|
|
600
|
-
writeFileSync2(configPath, getDefaultConfig(agent));
|
|
601
|
-
}
|
|
602
|
-
createDefaultSteps(spetsDir);
|
|
603
|
-
createClaudeCommand(cwd);
|
|
604
|
-
printEnhancedInitOutput(options);
|
|
605
|
-
}
|
|
606
|
-
function createDefaultSteps(spetsDir) {
|
|
607
|
-
const planDir = join2(spetsDir, "steps", "01-plan");
|
|
608
|
-
mkdirSync2(planDir, { recursive: true });
|
|
609
|
-
const planTemplatePath = join2(planDir, "template.md");
|
|
610
|
-
if (!existsSync2(planTemplatePath)) {
|
|
611
|
-
writeFileSync2(planTemplatePath, getPlanTemplate());
|
|
612
|
-
}
|
|
613
|
-
const implementDir = join2(spetsDir, "steps", "02-implement");
|
|
614
|
-
mkdirSync2(implementDir, { recursive: true });
|
|
615
|
-
const implementTemplatePath = join2(implementDir, "template.md");
|
|
616
|
-
if (!existsSync2(implementTemplatePath)) {
|
|
617
|
-
writeFileSync2(implementTemplatePath, getImplementTemplate());
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
function getPlanTemplate() {
|
|
621
|
-
const fullTemplate = readFileSync(join2(__dirname, "..", "templates", "steps", "01-plan", "template.md"), "utf-8");
|
|
622
|
-
return fullTemplate;
|
|
623
|
-
}
|
|
624
|
-
function getImplementTemplate() {
|
|
625
|
-
const fullTemplate = readFileSync(join2(__dirname, "..", "templates", "steps", "02-implement", "template.md"), "utf-8");
|
|
626
|
-
return fullTemplate;
|
|
627
|
-
}
|
|
628
|
-
function createClaudeCommand(cwd) {
|
|
629
|
-
const commandDir = join2(cwd, ".claude", "commands");
|
|
630
|
-
mkdirSync2(commandDir, { recursive: true });
|
|
631
|
-
writeFileSync2(join2(commandDir, "spets.md"), getClaudeSkillContent());
|
|
632
|
-
}
|
|
690
|
+
- NEVER skip or modify orchestrator commands
|
|
691
|
+
- NEVER improvise steps \u2014 only do what instructions say
|
|
692
|
+
- Always pass JSON output as a single-quoted string argument
|
|
693
|
+
- Minify JSON when passing as command arguments
|
|
633
694
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const config = loadConfig(cwd);
|
|
642
|
-
if (options.task) {
|
|
643
|
-
showTaskStatus(options.task, config, cwd);
|
|
644
|
-
} else {
|
|
645
|
-
showAllTasks(config, cwd);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
function showTaskStatus(taskId, config, cwd) {
|
|
649
|
-
const state = getWorkflowState(taskId, config, cwd);
|
|
650
|
-
const meta = loadTaskMetadata(taskId, cwd);
|
|
651
|
-
if (!state) {
|
|
652
|
-
console.error(`Task '${taskId}' not found.`);
|
|
653
|
-
process.exit(1);
|
|
654
|
-
}
|
|
655
|
-
console.log(`Task: ${taskId}`);
|
|
656
|
-
console.log(`Query: ${meta?.userQuery || state.userQuery || "(unknown)"}`);
|
|
657
|
-
console.log(`Status: ${formatStatus(state.status)}`);
|
|
658
|
-
console.log(`Current Step: ${state.currentStepName} (${state.currentStepIndex + 1}/${config.steps.length})`);
|
|
659
|
-
console.log("");
|
|
660
|
-
console.log("Steps:");
|
|
661
|
-
for (let i = 0; i < config.steps.length; i++) {
|
|
662
|
-
const stepName = config.steps[i];
|
|
663
|
-
const output = state.outputs.get(stepName);
|
|
664
|
-
const isCurrent = i === state.currentStepIndex;
|
|
665
|
-
const marker = isCurrent ? "\u2192" : " ";
|
|
666
|
-
const status = output ? formatDocStatus(output.status) : "\u25CB";
|
|
667
|
-
console.log(` ${marker} ${i + 1}. ${stepName} ${status}`);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
function showAllTasks(config, cwd) {
|
|
671
|
-
const tasks = listTasks(cwd);
|
|
672
|
-
if (tasks.length === 0) {
|
|
673
|
-
console.log("No tasks found.");
|
|
674
|
-
console.log("");
|
|
675
|
-
console.log('Start a new task with: spets orchestrate init "your task description"');
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
console.log("Tasks:");
|
|
679
|
-
console.log("");
|
|
680
|
-
for (const taskId of tasks.slice(0, 10)) {
|
|
681
|
-
const state = getWorkflowState(taskId, config, cwd);
|
|
682
|
-
const meta = loadTaskMetadata(taskId, cwd);
|
|
683
|
-
if (!state) continue;
|
|
684
|
-
const query = meta?.userQuery || state.userQuery || "(no query)";
|
|
685
|
-
const truncatedQuery = query.length > 50 ? query.substring(0, 47) + "..." : query;
|
|
686
|
-
const progress = `${state.currentStepIndex + 1}/${config.steps.length}`;
|
|
687
|
-
console.log(` ${taskId} ${formatStatus(state.status)} [${progress}]`);
|
|
688
|
-
console.log(` ${truncatedQuery}`);
|
|
689
|
-
console.log("");
|
|
690
|
-
}
|
|
691
|
-
if (tasks.length > 10) {
|
|
692
|
-
console.log(` ... and ${tasks.length - 10} more tasks`);
|
|
693
|
-
}
|
|
694
|
-
console.log("");
|
|
695
|
-
console.log('Use "spets status -t <taskId>" for details');
|
|
696
|
-
}
|
|
697
|
-
function formatStatus(status) {
|
|
698
|
-
const icons = {
|
|
699
|
-
in_progress: "\u{1F504} in_progress",
|
|
700
|
-
completed: "\u2705 completed",
|
|
701
|
-
paused: "\u23F8\uFE0F paused",
|
|
702
|
-
rejected: "\u274C rejected"
|
|
703
|
-
};
|
|
704
|
-
return icons[status] || status;
|
|
705
|
-
}
|
|
706
|
-
function formatDocStatus(status) {
|
|
707
|
-
const icons = {
|
|
708
|
-
draft: "\u25D0",
|
|
709
|
-
approved: "\u25CF",
|
|
710
|
-
rejected: "\u2717"
|
|
711
|
-
};
|
|
712
|
-
return icons[status] || "\u25CB";
|
|
695
|
+
## Forbidden
|
|
696
|
+
|
|
697
|
+
Planning mode, task tracking tools
|
|
698
|
+
|
|
699
|
+
$ARGUMENTS
|
|
700
|
+
description: Task description for the workflow (or "resume" to list/continue previous workflows)
|
|
701
|
+
`;
|
|
713
702
|
}
|
|
714
703
|
|
|
715
704
|
// src/orchestrator/index.ts
|
|
@@ -1169,6 +1158,9 @@ function buildExecutePrompt(params) {
|
|
|
1169
1158
|
parts.push("- **Use your full capabilities.** Whatever this output requires \u2014 do it.");
|
|
1170
1159
|
parts.push("- **Be context-efficient.** Use the explored context above as your primary reference. Use Grep to locate code before reading. Never re-read a file you have already read in this session.");
|
|
1171
1160
|
parts.push("");
|
|
1161
|
+
if (config.team?.enabled) {
|
|
1162
|
+
parts.push(buildExecuteTeamSection());
|
|
1163
|
+
}
|
|
1172
1164
|
parts.push("## Output");
|
|
1173
1165
|
parts.push("");
|
|
1174
1166
|
parts.push(`Produce the output and save to: \`${outputPath}\``);
|
|
@@ -1187,21 +1179,50 @@ function buildExecutePrompt(params) {
|
|
|
1187
1179
|
outputPath
|
|
1188
1180
|
};
|
|
1189
1181
|
}
|
|
1182
|
+
function buildExecuteTeamSection() {
|
|
1183
|
+
return `## Agent Team Instructions
|
|
1184
|
+
|
|
1185
|
+
**You have access to Agent Teams (TeammateTool).** Use them to produce higher-quality output.
|
|
1186
|
+
|
|
1187
|
+
Spawn an agent team with 3 teammates:
|
|
1188
|
+
|
|
1189
|
+
1. **context-verifier** \u2014 Re-check that the explored context is still current.
|
|
1190
|
+
- Verify referenced files still exist and snippets are accurate
|
|
1191
|
+
- Flag any stale context that needs updating
|
|
1192
|
+
- Report: list of confirmed/stale context items
|
|
1193
|
+
|
|
1194
|
+
2. **drafter** \u2014 Write the output document following the template.
|
|
1195
|
+
- Use the explored context, user answers, and template
|
|
1196
|
+
- Produce the complete document with required frontmatter
|
|
1197
|
+
- Report: the full draft document
|
|
1198
|
+
|
|
1199
|
+
3. **quality-checker** \u2014 Review the draft for completeness and correctness.
|
|
1200
|
+
- Check for placeholders, TODOs, or missing sections
|
|
1201
|
+
- Verify claims against the codebase
|
|
1202
|
+
- Report: list of issues with suggestions
|
|
1203
|
+
|
|
1204
|
+
**Workflow:**
|
|
1205
|
+
- Run **context-verifier** and **drafter** in parallel
|
|
1206
|
+
- Once drafter completes, send draft to **quality-checker** for review
|
|
1207
|
+
- As the lead, synthesize: apply quality-checker fixes, incorporate context-verifier updates
|
|
1208
|
+
- Produce the final output
|
|
1209
|
+
|
|
1210
|
+
**Fallback:** If TeammateTool is not available (Agent Teams not enabled),
|
|
1211
|
+
perform all work yourself. The output format is the same either way.
|
|
1212
|
+
`;
|
|
1213
|
+
}
|
|
1190
1214
|
|
|
1191
1215
|
// src/core/prompts/verify.ts
|
|
1192
1216
|
import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
|
|
1193
1217
|
import { join as join8 } from "path";
|
|
1194
|
-
function
|
|
1218
|
+
function buildVerifySharedContext(params) {
|
|
1195
1219
|
const cwd = params.cwd || process.cwd();
|
|
1196
1220
|
const stepsDir = getStepsDir(cwd);
|
|
1197
1221
|
const templatePath = join8(stepsDir, params.step, "template.md");
|
|
1198
1222
|
const template = existsSync9(templatePath) ? readFileSync8(templatePath, "utf-8") : "";
|
|
1199
1223
|
const document = existsSync9(params.documentPath) ? readFileSync8(params.documentPath, "utf-8") : "";
|
|
1224
|
+
const config = loadConfig(cwd);
|
|
1200
1225
|
const parts = [];
|
|
1201
|
-
parts.push("# Verify Phase");
|
|
1202
|
-
parts.push("");
|
|
1203
|
-
parts.push("Verify that this output is accurate, complete, and consistent.");
|
|
1204
|
-
parts.push("");
|
|
1205
1226
|
parts.push(`**Attempt ${params.verifyAttempts} of 3**`);
|
|
1206
1227
|
parts.push("");
|
|
1207
1228
|
parts.push("## Task Information");
|
|
@@ -1221,7 +1242,6 @@ function buildVerifyPrompt(params) {
|
|
|
1221
1242
|
parts.push(template);
|
|
1222
1243
|
parts.push("");
|
|
1223
1244
|
}
|
|
1224
|
-
const config = loadConfig(cwd);
|
|
1225
1245
|
if (config.knowledge?.inject !== false) {
|
|
1226
1246
|
const knowledgeSection = buildKnowledgeSummarySection(cwd);
|
|
1227
1247
|
if (knowledgeSection) {
|
|
@@ -1235,6 +1255,16 @@ function buildVerifyPrompt(params) {
|
|
|
1235
1255
|
parts.push("");
|
|
1236
1256
|
}
|
|
1237
1257
|
}
|
|
1258
|
+
return { template, document, sharedContext: parts.join("\n"), config };
|
|
1259
|
+
}
|
|
1260
|
+
function buildVerifyPrompt(params) {
|
|
1261
|
+
const { sharedContext, config } = buildVerifySharedContext(params);
|
|
1262
|
+
const parts = [];
|
|
1263
|
+
parts.push("# Verify Phase");
|
|
1264
|
+
parts.push("");
|
|
1265
|
+
parts.push("Verify that this output is accurate, complete, and consistent.");
|
|
1266
|
+
parts.push("");
|
|
1267
|
+
parts.push(sharedContext);
|
|
1238
1268
|
parts.push("## Verification Criteria");
|
|
1239
1269
|
parts.push("");
|
|
1240
1270
|
parts.push("1. **Accuracy** (0-100)");
|
|
@@ -1291,39 +1321,37 @@ function buildVerifyPrompt(params) {
|
|
|
1291
1321
|
return parts.join("\n");
|
|
1292
1322
|
}
|
|
1293
1323
|
function buildVerifyTeamSection() {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
lines.push("");
|
|
1326
|
-
return lines.join("\n");
|
|
1324
|
+
return `## Agent Team Instructions
|
|
1325
|
+
|
|
1326
|
+
**You have access to Agent Teams (TeammateTool).** Use them for independent verification.
|
|
1327
|
+
|
|
1328
|
+
Spawn an agent team with 2 teammates:
|
|
1329
|
+
|
|
1330
|
+
1. **accuracy-verifier** \u2014 Verify that claims in the output are true.
|
|
1331
|
+
- Check referenced files exist, described changes are real
|
|
1332
|
+
- Verify cited patterns match the codebase
|
|
1333
|
+
- Score: accuracy
|
|
1334
|
+
- List any issues found with severity and suggestion
|
|
1335
|
+
|
|
1336
|
+
2. **completeness-verifier** \u2014 Verify completeness and consistency.
|
|
1337
|
+
- Check the output covers what the template asks for
|
|
1338
|
+
- Verify no placeholders, TODOs, or missing sections
|
|
1339
|
+
- Check internal consistency, no contradictions
|
|
1340
|
+
- Score: completeness, consistency
|
|
1341
|
+
- List any issues found with severity and suggestion
|
|
1342
|
+
|
|
1343
|
+
**Key principle:** Writer \u2260 Reviewer. The teammates provide independent review
|
|
1344
|
+
of a document they did not write, reducing blind spots.
|
|
1345
|
+
|
|
1346
|
+
**As the lead agent**, you should:
|
|
1347
|
+
- Send each teammate the document and their specific evaluation criteria
|
|
1348
|
+
- Wait for both teammates to complete
|
|
1349
|
+
- Combine their scores and issues into the standard VerifyOutput JSON
|
|
1350
|
+
- Determine the final passed/failed status based on pass criteria
|
|
1351
|
+
|
|
1352
|
+
**Fallback:** If TeammateTool is not available (Agent Teams not enabled),
|
|
1353
|
+
perform all verification yourself. The output format is the same either way.
|
|
1354
|
+
`;
|
|
1327
1355
|
}
|
|
1328
1356
|
|
|
1329
1357
|
// src/core/prompts/knowledge.ts
|
|
@@ -1472,6 +1500,21 @@ var LIST_TASKS = "Show tasks to user. Ask which to resume. Run:\n npx spets orc
|
|
|
1472
1500
|
var LIST_EMPTY = "No resumable tasks found. Show this to the user. Stop.";
|
|
1473
1501
|
|
|
1474
1502
|
// src/orchestrator/responses.ts
|
|
1503
|
+
function getOrchestrateCommandPrefix() {
|
|
1504
|
+
const override = process.env.SPETS_ORCHESTRATE_CMD?.trim();
|
|
1505
|
+
if (override) {
|
|
1506
|
+
return override;
|
|
1507
|
+
}
|
|
1508
|
+
const entryScript = process.argv[1];
|
|
1509
|
+
if (entryScript && existsSync10(entryScript)) {
|
|
1510
|
+
const escapedPath = entryScript.includes(" ") ? `"${entryScript.replace(/"/g, '\\"')}"` : entryScript;
|
|
1511
|
+
return `node ${escapedPath} orchestrate`;
|
|
1512
|
+
}
|
|
1513
|
+
return "npx --yes spets orchestrate";
|
|
1514
|
+
}
|
|
1515
|
+
function buildOrchestrateCommand(args) {
|
|
1516
|
+
return `${getOrchestrateCommandPrefix()} ${args}`;
|
|
1517
|
+
}
|
|
1475
1518
|
function buildPhaseExploreResponse(cwd, state) {
|
|
1476
1519
|
const steps = getSteps(cwd);
|
|
1477
1520
|
const outputPath = getOutputPath(cwd);
|
|
@@ -1492,7 +1535,7 @@ function buildPhaseExploreResponse(cwd, state) {
|
|
|
1492
1535
|
previousOutput,
|
|
1493
1536
|
cwd
|
|
1494
1537
|
});
|
|
1495
|
-
const onComplete = `
|
|
1538
|
+
const onComplete = buildOrchestrateCommand(`explore-done ${state.taskId}`);
|
|
1496
1539
|
return {
|
|
1497
1540
|
type: "phase",
|
|
1498
1541
|
phase: "explore",
|
|
@@ -1521,7 +1564,7 @@ function buildPhaseClarifyResponse(cwd, state) {
|
|
|
1521
1564
|
// Legacy support
|
|
1522
1565
|
cwd
|
|
1523
1566
|
});
|
|
1524
|
-
const onComplete = `
|
|
1567
|
+
const onComplete = buildOrchestrateCommand(`clarify-done ${state.taskId}`);
|
|
1525
1568
|
return {
|
|
1526
1569
|
type: "phase",
|
|
1527
1570
|
phase: "clarify",
|
|
@@ -1577,7 +1620,7 @@ ${issues}`;
|
|
|
1577
1620
|
verifyFeedback,
|
|
1578
1621
|
cwd
|
|
1579
1622
|
});
|
|
1580
|
-
const onComplete = `
|
|
1623
|
+
const onComplete = buildOrchestrateCommand(`execute-done ${state.taskId}`);
|
|
1581
1624
|
const outputFile = join9(outputPath, state.taskId, `${state.currentStep}.md`);
|
|
1582
1625
|
return {
|
|
1583
1626
|
type: "phase",
|
|
@@ -1617,7 +1660,7 @@ function buildPhaseVerifyResponse(cwd, state) {
|
|
|
1617
1660
|
loadedKnowledge: state.loadedKnowledge?.map((k) => ({ filename: k.filename, content: k.content })),
|
|
1618
1661
|
cwd
|
|
1619
1662
|
});
|
|
1620
|
-
const onComplete = `
|
|
1663
|
+
const onComplete = buildOrchestrateCommand(`verify-done ${state.taskId}`);
|
|
1621
1664
|
return {
|
|
1622
1665
|
type: "phase",
|
|
1623
1666
|
phase: "verify",
|
|
@@ -1647,7 +1690,7 @@ function buildPhaseKnowledgeResponse(cwd, state) {
|
|
|
1647
1690
|
guide,
|
|
1648
1691
|
cwd
|
|
1649
1692
|
});
|
|
1650
|
-
const onComplete = `
|
|
1693
|
+
const onComplete = buildOrchestrateCommand(`knowledge-extract-done ${state.taskId}`);
|
|
1651
1694
|
return {
|
|
1652
1695
|
type: "phase",
|
|
1653
1696
|
phase: "knowledge",
|
|
@@ -1665,7 +1708,7 @@ function buildPhaseKnowledgeResponse(cwd, state) {
|
|
|
1665
1708
|
};
|
|
1666
1709
|
}
|
|
1667
1710
|
function buildCheckpointClarifyResponse(state) {
|
|
1668
|
-
const onComplete = `
|
|
1711
|
+
const onComplete = buildOrchestrateCommand(`clarified ${state.taskId}`);
|
|
1669
1712
|
return {
|
|
1670
1713
|
type: "checkpoint",
|
|
1671
1714
|
checkpoint: "clarify",
|
|
@@ -1680,10 +1723,10 @@ function buildCheckpointApproveResponse(cwd, state) {
|
|
|
1680
1723
|
const outputPath = getOutputPath(cwd);
|
|
1681
1724
|
const specPath = join9(outputPath, state.taskId, `${state.currentStep}.md`);
|
|
1682
1725
|
const cmds = {
|
|
1683
|
-
approve: `
|
|
1684
|
-
revise: `
|
|
1685
|
-
reject: `
|
|
1686
|
-
stop: `
|
|
1726
|
+
approve: buildOrchestrateCommand(`approve ${state.taskId}`),
|
|
1727
|
+
revise: buildOrchestrateCommand(`revise ${state.taskId} '<feedback>'`),
|
|
1728
|
+
reject: buildOrchestrateCommand(`reject ${state.taskId}`),
|
|
1729
|
+
stop: buildOrchestrateCommand(`stop ${state.taskId}`)
|
|
1687
1730
|
};
|
|
1688
1731
|
return {
|
|
1689
1732
|
type: "checkpoint",
|
|
@@ -1710,7 +1753,7 @@ function buildCompleteResponse(cwd, state, status) {
|
|
|
1710
1753
|
}
|
|
1711
1754
|
const messages = {
|
|
1712
1755
|
completed: "Workflow completed successfully",
|
|
1713
|
-
stopped: `Workflow stopped. Resume with:
|
|
1756
|
+
stopped: `Workflow stopped. Resume with: ${buildOrchestrateCommand(`resume ${state.taskId}`)}`,
|
|
1714
1757
|
rejected: "Workflow rejected"
|
|
1715
1758
|
};
|
|
1716
1759
|
return {
|