@smithers-orchestrator/cli 0.16.0
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/LICENSE +21 -0
- package/package.json +55 -0
- package/src/AgentAvailability.ts +13 -0
- package/src/AgentAvailabilityStatus.ts +5 -0
- package/src/AggregateNodeDetailParams.ts +5 -0
- package/src/AskOptions.ts +12 -0
- package/src/ChatAttemptMeta.ts +7 -0
- package/src/ChatAttemptRow.ts +12 -0
- package/src/ChatOutputEvent.ts +6 -0
- package/src/DiffBundleLike.ts +6 -0
- package/src/DiscoveredWorkflow.ts +9 -0
- package/src/EnrichedNodeDetail.ts +60 -0
- package/src/EventCategory.ts +18 -0
- package/src/FindDbWaitOptions.ts +4 -0
- package/src/FormatEventLineOptions.ts +4 -0
- package/src/HijackCandidate.ts +11 -0
- package/src/HijackLaunchSpec.ts +6 -0
- package/src/InitWorkflowPackOptions.ts +4 -0
- package/src/InitWorkflowPackResult.ts +6 -0
- package/src/NativeHijackEngine.ts +8 -0
- package/src/NodeDetailAttempt.ts +22 -0
- package/src/NodeDetailTokenUsage.ts +11 -0
- package/src/NodeDetailToolCall.ts +12 -0
- package/src/ParsedNodeOutputEvent.ts +9 -0
- package/src/RenderNodeDetailOptions.ts +4 -0
- package/src/RunAutoResumeSkipReason.ts +4 -0
- package/src/RunDiffCommandInput.ts +13 -0
- package/src/RunDiffCommandResult.ts +3 -0
- package/src/RunOutputCommandInput.ts +12 -0
- package/src/RunOutputCommandResult.ts +3 -0
- package/src/RunRewindCommandInput.ts +14 -0
- package/src/RunRewindCommandResult.ts +3 -0
- package/src/RunTreeCommandInput.ts +14 -0
- package/src/RunTreeCommandResult.ts +3 -0
- package/src/SmithersEventType.ts +3 -0
- package/src/SupervisorOptions.ts +33 -0
- package/src/SupervisorPollSummary.ts +6 -0
- package/src/TreeRenderOptions.ts +5 -0
- package/src/WatchLoopOptions.ts +9 -0
- package/src/WatchLoopResult.ts +8 -0
- package/src/WatchRenderContext.ts +4 -0
- package/src/WhyBlocker.ts +17 -0
- package/src/WhyBlockerKind.ts +9 -0
- package/src/WhyDiagnosis.ts +10 -0
- package/src/WorkflowCta.ts +4 -0
- package/src/WorkflowSourceType.ts +1 -0
- package/src/agent-detection.js +257 -0
- package/src/ask.js +491 -0
- package/src/chat.js +226 -0
- package/src/diff.js +221 -0
- package/src/event-categories.js +141 -0
- package/src/find-db.js +93 -0
- package/src/format.js +272 -0
- package/src/hijack-session.js +207 -0
- package/src/hijack.js +226 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +4868 -0
- package/src/mcp/SemanticMcpServerOptions.ts +4 -0
- package/src/mcp/SemanticToolCallResult.ts +14 -0
- package/src/mcp/SemanticToolContext.ts +6 -0
- package/src/mcp/SemanticToolDefinition.ts +13 -0
- package/src/mcp/SemanticToolError.ts +6 -0
- package/src/mcp/semantic-server.js +41 -0
- package/src/mcp/semantic-tools.js +1242 -0
- package/src/node-detail.js +682 -0
- package/src/output.js +111 -0
- package/src/resume-detached.js +37 -0
- package/src/rewind.js +88 -0
- package/src/scheduler.js +112 -0
- package/src/smithersRuntime.js +63 -0
- package/src/supervisor.js +418 -0
- package/src/tree.js +307 -0
- package/src/tui/app.jsx +139 -0
- package/src/tui/app.tsx +5 -0
- package/src/tui/components/AskModal.jsx +109 -0
- package/src/tui/components/AskModal.tsx +3 -0
- package/src/tui/components/AttentionPane.jsx +112 -0
- package/src/tui/components/AttentionPane.tsx +6 -0
- package/src/tui/components/ChatPane.jsx +57 -0
- package/src/tui/components/ChatPane.tsx +7 -0
- package/src/tui/components/CronList.jsx +87 -0
- package/src/tui/components/CronList.tsx +5 -0
- package/src/tui/components/DetailsPane.jsx +96 -0
- package/src/tui/components/DetailsPane.tsx +7 -0
- package/src/tui/components/FramesPane.jsx +147 -0
- package/src/tui/components/FramesPane.tsx +8 -0
- package/src/tui/components/LogsPane.jsx +46 -0
- package/src/tui/components/LogsPane.tsx +6 -0
- package/src/tui/components/MetricsPane.jsx +108 -0
- package/src/tui/components/MetricsPane.tsx +5 -0
- package/src/tui/components/NodeDetailView.jsx +284 -0
- package/src/tui/components/NodeDetailView.tsx +7 -0
- package/src/tui/components/NodeInspector.jsx +51 -0
- package/src/tui/components/NodeInspector.tsx +7 -0
- package/src/tui/components/RunDetailView.jsx +190 -0
- package/src/tui/components/RunDetailView.tsx +7 -0
- package/src/tui/components/RunsList.jsx +184 -0
- package/src/tui/components/RunsList.tsx +7 -0
- package/src/tui/components/SqliteBrowser.jsx +131 -0
- package/src/tui/components/SqliteBrowser.tsx +5 -0
- package/src/tui/components/WorkflowLauncher.jsx +63 -0
- package/src/tui/components/WorkflowLauncher.tsx +3 -0
- package/src/util/CliErrorMapping.ts +7 -0
- package/src/util/CliExitCode.ts +10 -0
- package/src/util/errorMessage.js +212 -0
- package/src/util/exitCodes.js +18 -0
- package/src/watch.js +128 -0
- package/src/why-diagnosis.js +1000 -0
- package/src/workflow-pack.js +2151 -0
- package/src/workflows.js +122 -0
|
@@ -0,0 +1,2151 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { generateAgentsTs } from "./agent-detection.js";
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{ force?: boolean; rootDir?: string; }} InitOptions
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {{ rootDir: string; writtenFiles: string[]; skippedFiles: string[]; preservedPaths: string[]; }} InitResult
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {{ command: string; description: string; }} WorkflowCta
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} path
|
|
16
|
+
*/
|
|
17
|
+
function ensureDir(path) {
|
|
18
|
+
mkdirSync(path, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} path
|
|
22
|
+
*/
|
|
23
|
+
function ensureParent(path) {
|
|
24
|
+
ensureDir(dirname(path));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} path
|
|
28
|
+
*/
|
|
29
|
+
function readJson(path) {
|
|
30
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} path
|
|
34
|
+
* @param {string} fallback
|
|
35
|
+
*/
|
|
36
|
+
function readPackageVersion(path, fallback) {
|
|
37
|
+
try {
|
|
38
|
+
return String(readJson(path).version ?? fallback);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* @returns {DependencyVersions}
|
|
46
|
+
*/
|
|
47
|
+
function readDependencyVersions() {
|
|
48
|
+
const rootPackage = readJson(new URL("../../../package.json", import.meta.url).pathname);
|
|
49
|
+
const nodeModulesRoot = new URL("../../../node_modules/", import.meta.url).pathname;
|
|
50
|
+
return {
|
|
51
|
+
smithersVersion: String(rootPackage.version ?? "0.0.0"),
|
|
52
|
+
zodVersion: readPackageVersion(resolve(nodeModulesRoot, "zod", "package.json"), "4.0.0"),
|
|
53
|
+
typescriptVersion: readPackageVersion(resolve(nodeModulesRoot, "typescript", "package.json"), "5.0.0"),
|
|
54
|
+
reactTypesVersion: readPackageVersion(resolve(nodeModulesRoot, "@types", "react", "package.json"), "19.0.0"),
|
|
55
|
+
reactDomTypesVersion: readPackageVersion(resolve(nodeModulesRoot, "@types", "react-dom", "package.json"), "19.0.0"),
|
|
56
|
+
mdxTypesVersion: readPackageVersion(resolve(nodeModulesRoot, "@types", "mdx", "package.json"), "2.0.0"),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @param {DependencyVersions} versions
|
|
61
|
+
*/
|
|
62
|
+
function renderPackageJson(versions) {
|
|
63
|
+
return JSON.stringify({
|
|
64
|
+
name: "smithers-workflows",
|
|
65
|
+
private: true,
|
|
66
|
+
type: "module",
|
|
67
|
+
scripts: {
|
|
68
|
+
typecheck: "tsc --noEmit",
|
|
69
|
+
"workflow:list": "smithers workflow list",
|
|
70
|
+
"workflow:run": "smithers workflow run",
|
|
71
|
+
"workflow:implement": "smithers workflow implement",
|
|
72
|
+
},
|
|
73
|
+
dependencies: {
|
|
74
|
+
"smithers-orchestrator": "latest",
|
|
75
|
+
zod: versions.zodVersion,
|
|
76
|
+
},
|
|
77
|
+
devDependencies: {
|
|
78
|
+
typescript: versions.typescriptVersion,
|
|
79
|
+
"@types/react": versions.reactTypesVersion,
|
|
80
|
+
"@types/react-dom": versions.reactDomTypesVersion,
|
|
81
|
+
"@types/mdx": versions.mdxTypesVersion,
|
|
82
|
+
},
|
|
83
|
+
}, null, 2) + "\n";
|
|
84
|
+
}
|
|
85
|
+
function renderTsconfig() {
|
|
86
|
+
return JSON.stringify({
|
|
87
|
+
compilerOptions: {
|
|
88
|
+
lib: ["ESNext", "DOM", "DOM.Iterable"],
|
|
89
|
+
target: "ESNext",
|
|
90
|
+
module: "ESNext",
|
|
91
|
+
moduleDetection: "force",
|
|
92
|
+
jsx: "react-jsx",
|
|
93
|
+
jsxImportSource: "smithers-orchestrator",
|
|
94
|
+
moduleResolution: "bundler",
|
|
95
|
+
allowImportingTsExtensions: true,
|
|
96
|
+
verbatimModuleSyntax: true,
|
|
97
|
+
noEmit: true,
|
|
98
|
+
strict: true,
|
|
99
|
+
skipLibCheck: true,
|
|
100
|
+
baseUrl: ".",
|
|
101
|
+
paths: {
|
|
102
|
+
"~/*": ["./*"],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
include: ["./**/*"],
|
|
106
|
+
exclude: ["./executions/**/*"],
|
|
107
|
+
}, null, 2) + "\n";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* @returns {TemplateFile[]}
|
|
111
|
+
*/
|
|
112
|
+
function renderPrompts() {
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
path: ".smithers/prompts/review.mdx",
|
|
116
|
+
contents: [
|
|
117
|
+
"# Review",
|
|
118
|
+
"",
|
|
119
|
+
"Reviewer: {props.reviewer}",
|
|
120
|
+
"",
|
|
121
|
+
"Review the following request and respond with a concise JSON object.",
|
|
122
|
+
"Be a very thorough reviewer who only accepts production ready tested",
|
|
123
|
+
"code.",
|
|
124
|
+
"",
|
|
125
|
+
"REQUEST:",
|
|
126
|
+
"{props.prompt}",
|
|
127
|
+
"",
|
|
128
|
+
"REQUIRED OUTPUT:",
|
|
129
|
+
"{props.schema}",
|
|
130
|
+
"",
|
|
131
|
+
].join("\n"),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: ".smithers/prompts/plan.mdx",
|
|
135
|
+
contents: [
|
|
136
|
+
"# Plan",
|
|
137
|
+
"",
|
|
138
|
+
"Create a practical implementation plan for the following request.",
|
|
139
|
+
"",
|
|
140
|
+
"REQUEST:",
|
|
141
|
+
"{props.prompt}",
|
|
142
|
+
"",
|
|
143
|
+
"REQUIRED OUTPUT:",
|
|
144
|
+
"{props.schema}",
|
|
145
|
+
"",
|
|
146
|
+
].join("\n"),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: ".smithers/prompts/implement.mdx",
|
|
150
|
+
contents: [
|
|
151
|
+
"# Implement",
|
|
152
|
+
"",
|
|
153
|
+
"Carry out the following request in the current repository.",
|
|
154
|
+
"",
|
|
155
|
+
"REQUEST:",
|
|
156
|
+
"{props.prompt}",
|
|
157
|
+
"",
|
|
158
|
+
"REQUIRED OUTPUT:",
|
|
159
|
+
"{props.schema}",
|
|
160
|
+
"",
|
|
161
|
+
].join("\n"),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
path: ".smithers/prompts/validate.mdx",
|
|
165
|
+
contents: [
|
|
166
|
+
"# Validate",
|
|
167
|
+
"",
|
|
168
|
+
"Validate the current repository state for the following request.",
|
|
169
|
+
"",
|
|
170
|
+
"REQUEST:",
|
|
171
|
+
"{props.prompt}",
|
|
172
|
+
"",
|
|
173
|
+
"REQUIRED OUTPUT:",
|
|
174
|
+
"{props.schema}",
|
|
175
|
+
"",
|
|
176
|
+
].join("\n"),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: ".smithers/prompts/coverage.mdx",
|
|
180
|
+
contents: [
|
|
181
|
+
"# Improve Test Coverage",
|
|
182
|
+
"",
|
|
183
|
+
"Identify the highest-impact missing tests for this request and add them.",
|
|
184
|
+
"",
|
|
185
|
+
"REQUEST:",
|
|
186
|
+
"{props.prompt}",
|
|
187
|
+
"",
|
|
188
|
+
"REQUIRED OUTPUT:",
|
|
189
|
+
"{props.schema}",
|
|
190
|
+
"",
|
|
191
|
+
].join("\n"),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
path: ".smithers/prompts/ticket.mdx",
|
|
195
|
+
contents: [
|
|
196
|
+
"# Ticket",
|
|
197
|
+
"",
|
|
198
|
+
"Create a well-structured ticket for the following request.",
|
|
199
|
+
"Include a clear title, detailed description, and acceptance criteria.",
|
|
200
|
+
"",
|
|
201
|
+
"REQUEST:",
|
|
202
|
+
"{props.prompt}",
|
|
203
|
+
"",
|
|
204
|
+
"REQUIRED OUTPUT:",
|
|
205
|
+
"{props.schema}",
|
|
206
|
+
"",
|
|
207
|
+
].join("\n"),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
path: ".smithers/prompts/research.mdx",
|
|
211
|
+
contents: [
|
|
212
|
+
"# Research",
|
|
213
|
+
"",
|
|
214
|
+
"Research the following request thoroughly. Gather relevant context,",
|
|
215
|
+
"prior art, and technical details needed to inform the implementation. ",
|
|
216
|
+
"Clone repos (if not already cloned) of external dependencies or websearch",
|
|
217
|
+
"for docs. Include working code from docs or from test cases in the repo",
|
|
218
|
+
"in your research. Understand codebase and point to relavent files. Think hard",
|
|
219
|
+
"about what context may be useful and find it. Be very thorough and take",
|
|
220
|
+
"your time with research unless the task seems trivially easy not worhty of",
|
|
221
|
+
'research. If you tink research was a waste of time include "Waste of time!" in',
|
|
222
|
+
"your output.",
|
|
223
|
+
"",
|
|
224
|
+
"REQUEST:",
|
|
225
|
+
"{props.prompt}",
|
|
226
|
+
"",
|
|
227
|
+
"REQUIRED OUTPUT:",
|
|
228
|
+
"{props.schema}",
|
|
229
|
+
"",
|
|
230
|
+
].join("\n"),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
path: ".smithers/prompts/ask-user-instructions.mdx",
|
|
234
|
+
contents: [
|
|
235
|
+
"## Asking the User Questions",
|
|
236
|
+
"",
|
|
237
|
+
"When you need to ask the user a question, run this command in bash:",
|
|
238
|
+
"",
|
|
239
|
+
"```bash",
|
|
240
|
+
'bun .smithers/scripts/ask-user.ts "Your question here" --recommended "Your recommended answer"',
|
|
241
|
+
"```",
|
|
242
|
+
"",
|
|
243
|
+
"The command will block and return the user's answer on stdout.",
|
|
244
|
+
"You MUST use this tool to ask questions — do not just print questions to the output.",
|
|
245
|
+
"Ask one question at a time, wait for the answer, then proceed.",
|
|
246
|
+
"",
|
|
247
|
+
"Options:",
|
|
248
|
+
" --recommended, -r Your recommended answer (shown to the user)",
|
|
249
|
+
" --branch, -b Which decision branch this question relates to",
|
|
250
|
+
" --timeout, -t Seconds to wait for answer (default: 300)",
|
|
251
|
+
"",
|
|
252
|
+
].join("\n"),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
path: ".smithers/prompts/write-a-prd.mdx",
|
|
256
|
+
contents: [
|
|
257
|
+
"# Write a PRD",
|
|
258
|
+
"",
|
|
259
|
+
"You are writing a Product Requirements Document. Follow these steps,",
|
|
260
|
+
"skipping any you consider unnecessary:",
|
|
261
|
+
"",
|
|
262
|
+
"1. Ask the user for a long, detailed description of the problem they",
|
|
263
|
+
" want to solve and any potential ideas for solutions.",
|
|
264
|
+
"",
|
|
265
|
+
"2. Explore the repo to verify their assertions and understand the",
|
|
266
|
+
" current state of the codebase.",
|
|
267
|
+
"",
|
|
268
|
+
"3. Interview the user relentlessly about every aspect of this plan",
|
|
269
|
+
" until you reach a shared understanding. Walk down each branch of",
|
|
270
|
+
" the design tree, resolving dependencies between decisions",
|
|
271
|
+
" one-by-one. Ask questions one at a time. For each question,",
|
|
272
|
+
" provide your recommended answer. If a question can be answered",
|
|
273
|
+
" by exploring the codebase, explore the codebase instead.",
|
|
274
|
+
"",
|
|
275
|
+
"4. Sketch out the major modules you will need to build or modify.",
|
|
276
|
+
" Actively look for opportunities to extract deep modules that can",
|
|
277
|
+
" be tested in isolation. A deep module (as opposed to a shallow",
|
|
278
|
+
" module) is one which encapsulates a lot of functionality in a",
|
|
279
|
+
" simple, testable interface which rarely changes. Check with the",
|
|
280
|
+
" user that these modules match their expectations and which ones",
|
|
281
|
+
" they want tests written for.",
|
|
282
|
+
"",
|
|
283
|
+
"5. Once you have a complete understanding, write the PRD using the",
|
|
284
|
+
" template below.",
|
|
285
|
+
"",
|
|
286
|
+
"{props.additionalInstructions}",
|
|
287
|
+
"",
|
|
288
|
+
"CONTEXT:",
|
|
289
|
+
"{props.context}",
|
|
290
|
+
"",
|
|
291
|
+
"## PRD Template",
|
|
292
|
+
"",
|
|
293
|
+
"### Problem Statement",
|
|
294
|
+
"The problem from the user's perspective.",
|
|
295
|
+
"",
|
|
296
|
+
"### Solution",
|
|
297
|
+
"The solution from the user's perspective.",
|
|
298
|
+
"",
|
|
299
|
+
"### User Stories",
|
|
300
|
+
"A LONG numbered list. Format: As an <actor>, I want a <feature>,",
|
|
301
|
+
"so that <benefit>. Be extremely extensive.",
|
|
302
|
+
"",
|
|
303
|
+
"### Implementation Decisions",
|
|
304
|
+
"Modules to build/modify, interfaces, technical clarifications,",
|
|
305
|
+
"architectural decisions, schema changes, API contracts, and",
|
|
306
|
+
"specific interactions. Do NOT include file paths or code snippets.",
|
|
307
|
+
"",
|
|
308
|
+
"### Testing Decisions",
|
|
309
|
+
"What makes a good test (external behavior, not implementation",
|
|
310
|
+
"details), which modules will be tested, and prior art for tests.",
|
|
311
|
+
"",
|
|
312
|
+
"### Out of Scope",
|
|
313
|
+
"What is explicitly not covered.",
|
|
314
|
+
"",
|
|
315
|
+
"### Further Notes",
|
|
316
|
+
"Any additional context.",
|
|
317
|
+
"",
|
|
318
|
+
].join("\n"),
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
path: ".smithers/prompts/feature-enum-scan.mdx",
|
|
322
|
+
contents: [
|
|
323
|
+
"# Feature Enum Scan",
|
|
324
|
+
"",
|
|
325
|
+
"Analyze the entire repository and produce an exhaustive feature inventory.",
|
|
326
|
+
"Scan routes, services, CLI commands, UI views, SDK modules, jobs, workflows,",
|
|
327
|
+
"and any other shipped or materially stubbed product surface you can find.",
|
|
328
|
+
"",
|
|
329
|
+
"Rules:",
|
|
330
|
+
"",
|
|
331
|
+
"1. Be exhaustive. Prefer missing nothing over being overly concise.",
|
|
332
|
+
"2. Group features by domain using SCREAMING_SNAKE_CASE group names.",
|
|
333
|
+
"3. Use SCREAMING_SNAKE_CASE feature names that are specific and code-backed.",
|
|
334
|
+
"4. Split broad buckets into concrete, independently auditable features.",
|
|
335
|
+
"5. Run `git rev-parse HEAD` and include the commit hash in `lastCommitHash`.",
|
|
336
|
+
"6. Include a markdownBody that explains the grouping and notable edge cases.",
|
|
337
|
+
"",
|
|
338
|
+
"{props.additionalContext ? `ADDITIONAL CONTEXT:\\n${props.additionalContext}\\n` : \"\"}",
|
|
339
|
+
"",
|
|
340
|
+
"Example format (truncated from a larger production feature enum):",
|
|
341
|
+
"",
|
|
342
|
+
"```ts",
|
|
343
|
+
"export const FeatureGroups = {",
|
|
344
|
+
" PLATFORM_RUNTIME: [",
|
|
345
|
+
" \"PLATFORM_SERVER_BOOTSTRAP\",",
|
|
346
|
+
" \"PLATFORM_DB_INITIALIZATION\",",
|
|
347
|
+
" \"PLATFORM_SERVICE_REGISTRY_INITIALIZATION\",",
|
|
348
|
+
" \"PLATFORM_HTTP_MIDDLEWARE_REQUEST_ID\",",
|
|
349
|
+
" \"PLATFORM_HEALTHCHECK_ENDPOINTS\",",
|
|
350
|
+
" ],",
|
|
351
|
+
"",
|
|
352
|
+
" AUTH_AND_IDENTITY: [",
|
|
353
|
+
" \"AUTH_SIGN_IN_WITH_GITHUB_OAUTH\",",
|
|
354
|
+
" \"AUTH_SIGN_IN_WITH_PERSONAL_ACCESS_TOKEN\",",
|
|
355
|
+
" \"AUTH_SESSION_COOKIE_ISSUANCE\",",
|
|
356
|
+
" \"AUTH_PERSONAL_ACCESS_TOKEN_CREATE\",",
|
|
357
|
+
" \"AUTH_CLI_BROWSER_LOGIN\",",
|
|
358
|
+
" ],",
|
|
359
|
+
"",
|
|
360
|
+
" USER_ACCOUNT_AND_SETTINGS: [",
|
|
361
|
+
" \"USER_SELF_PROFILE_VIEW\",",
|
|
362
|
+
" \"USER_PROFILE_UPDATE\",",
|
|
363
|
+
" \"USER_SSH_KEY_ADD\",",
|
|
364
|
+
" \"USER_NOTIFICATION_SETTINGS_UPDATE\",",
|
|
365
|
+
" \"USER_API_TOKENS_UI\",",
|
|
366
|
+
" ],",
|
|
367
|
+
"};",
|
|
368
|
+
"```",
|
|
369
|
+
"",
|
|
370
|
+
"REQUIRED OUTPUT:",
|
|
371
|
+
"{props.schema}",
|
|
372
|
+
"",
|
|
373
|
+
].join("\n"),
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
path: ".smithers/prompts/feature-enum-refine.mdx",
|
|
377
|
+
contents: [
|
|
378
|
+
"# Feature Enum Refine",
|
|
379
|
+
"",
|
|
380
|
+
"Refine the existing feature inventory. Find missing features, split overly",
|
|
381
|
+
"broad items into concrete code-backed features, and keep the grouping stable",
|
|
382
|
+
"unless there is a strong repo-backed reason to reorganize it.",
|
|
383
|
+
"",
|
|
384
|
+
"Iteration: {props.iteration}",
|
|
385
|
+
"",
|
|
386
|
+
"{props.lastCommitHash ? `A previous inventory exists. Inspect repo deltas with:\\ngit log --oneline ${props.lastCommitHash}..HEAD\\n` : \"\"}",
|
|
387
|
+
"",
|
|
388
|
+
"CURRENT FEATURE GROUPS:",
|
|
389
|
+
"```json",
|
|
390
|
+
"{JSON.stringify(props.existingFeatures ?? {}, null, 2)}",
|
|
391
|
+
"```",
|
|
392
|
+
"",
|
|
393
|
+
"Checklist:",
|
|
394
|
+
"",
|
|
395
|
+
"1. Add any concrete missing features you can prove from the code.",
|
|
396
|
+
"2. Decompose vague or overloaded feature names into auditable units.",
|
|
397
|
+
"3. Preserve naming discipline: SCREAMING_SNAKE_CASE groups and features.",
|
|
398
|
+
"4. Remove only entries that are clearly unsupported by the current codebase.",
|
|
399
|
+
"5. Keep `lastCommitHash` current by running `git rev-parse HEAD`.",
|
|
400
|
+
"",
|
|
401
|
+
"REQUIRED OUTPUT:",
|
|
402
|
+
"{props.schema}",
|
|
403
|
+
"",
|
|
404
|
+
].join("\n"),
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
path: ".smithers/prompts/audit-feature.mdx",
|
|
408
|
+
contents: [
|
|
409
|
+
"# Audit Feature Group",
|
|
410
|
+
"",
|
|
411
|
+
"Audit the feature group below for the requested focus area.",
|
|
412
|
+
"",
|
|
413
|
+
"Group: {props.groupName}",
|
|
414
|
+
"Focus: {props.focus}",
|
|
415
|
+
"",
|
|
416
|
+
"Features:",
|
|
417
|
+
"{(props.features ?? []).map((feature) => `- ${feature}`).join(\"\\n\")}",
|
|
418
|
+
"",
|
|
419
|
+
"{props.additionalContext ? `ADDITIONAL CONTEXT:\\n${props.additionalContext}\\n` : \"\"}",
|
|
420
|
+
"",
|
|
421
|
+
"Review the code paths that implement these features. Identify concrete gaps,",
|
|
422
|
+
"risks, missing safeguards, and the most important follow-up actions.",
|
|
423
|
+
"",
|
|
424
|
+
"REQUIRED OUTPUT:",
|
|
425
|
+
"{props.schema}",
|
|
426
|
+
"",
|
|
427
|
+
].join("\n"),
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
path: ".smithers/prompts/merge-tickets.mdx",
|
|
431
|
+
contents: [
|
|
432
|
+
"# Merge Tickets",
|
|
433
|
+
"",
|
|
434
|
+
"Merge the completed ticket branches back into the main branch.",
|
|
435
|
+
"",
|
|
436
|
+
"The following tickets were implemented in worktree branches:",
|
|
437
|
+
"",
|
|
438
|
+
"{props.ticketSummary}",
|
|
439
|
+
"",
|
|
440
|
+
'For each branch with status "success":',
|
|
441
|
+
"1. git merge the branch into the current branch (main)",
|
|
442
|
+
"2. If there are merge conflicts, resolve them sensibly",
|
|
443
|
+
"3. If a branch cannot be cleanly merged, skip it and note it as conflicted",
|
|
444
|
+
"",
|
|
445
|
+
"Report which branches were merged and which had conflicts.",
|
|
446
|
+
"",
|
|
447
|
+
"REQUIRED OUTPUT:",
|
|
448
|
+
"{props.schema}",
|
|
449
|
+
"",
|
|
450
|
+
].join("\n"),
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
path: ".smithers/prompts/tickets-create.mdx",
|
|
454
|
+
contents: [
|
|
455
|
+
"# Tickets Create",
|
|
456
|
+
"",
|
|
457
|
+
"Break the following request into well-defined tickets with titles, descriptions, and acceptance criteria.",
|
|
458
|
+
"",
|
|
459
|
+
"Request: {props.prompt}",
|
|
460
|
+
"",
|
|
461
|
+
"REQUIRED OUTPUT:",
|
|
462
|
+
"{props.schema}",
|
|
463
|
+
"",
|
|
464
|
+
].join("\n"),
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
path: ".smithers/prompts/feature-task.mdx",
|
|
468
|
+
contents: [
|
|
469
|
+
'# {props.granularity === "feature" ? "Feature" : "Feature Group"} Task',
|
|
470
|
+
"",
|
|
471
|
+
"Group: {props.groupName}",
|
|
472
|
+
"Granularity: {props.granularity}",
|
|
473
|
+
"",
|
|
474
|
+
"Features:",
|
|
475
|
+
'{props.features.map((feature) => `- ${feature}`).join("\\n")}',
|
|
476
|
+
"",
|
|
477
|
+
"REQUEST:",
|
|
478
|
+
"{props.prompt}",
|
|
479
|
+
"",
|
|
480
|
+
"REQUIRED OUTPUT:",
|
|
481
|
+
"{props.schema}",
|
|
482
|
+
"",
|
|
483
|
+
].join("\n"),
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
path: ".smithers/prompts/audit.mdx",
|
|
487
|
+
contents: [
|
|
488
|
+
"Audit for: {props.focus}.",
|
|
489
|
+
"",
|
|
490
|
+
"Evaluate the provided feature scope for gaps in testing, observability, error handling, operational safety, and maintainability.",
|
|
491
|
+
"Use the repository as the source of truth and report concrete findings with actionable next steps.",
|
|
492
|
+
"",
|
|
493
|
+
'{props.additionalContext ? `Additional context:\\n${props.additionalContext}` : ""}',
|
|
494
|
+
"",
|
|
495
|
+
].join("\n"),
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
path: ".smithers/prompts/sweep-documentation.mdx",
|
|
499
|
+
contents: [
|
|
500
|
+
"Review documentation coverage for this feature group.",
|
|
501
|
+
"",
|
|
502
|
+
"Check docs/ for each feature listed. For every feature:",
|
|
503
|
+
"- Verify documentation exists and is accurate.",
|
|
504
|
+
"- If docs are missing, incomplete, or out of date — fix them directly.",
|
|
505
|
+
"- Improve clarity, add usage examples, and correct errors.",
|
|
506
|
+
"- Do NOT modify the README.",
|
|
507
|
+
"",
|
|
508
|
+
"You MUST succeed regardless of what you find. Fix any issues and report what you changed.",
|
|
509
|
+
"Score 0–100 based on documentation completeness AFTER your fixes.",
|
|
510
|
+
"",
|
|
511
|
+
].join("\n"),
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
path: ".smithers/prompts/sweep-e2e-testing.mdx",
|
|
515
|
+
contents: [
|
|
516
|
+
"Review end-to-end test coverage for this feature group.",
|
|
517
|
+
"",
|
|
518
|
+
"For every feature listed:",
|
|
519
|
+
"- Verify an e2e test exists with ZERO mocks — real dependencies only.",
|
|
520
|
+
"- Tests must cover all boundary conditions: maximum file sizes, maximum input lengths, empty inputs, extremely large inputs.",
|
|
521
|
+
"- If a value can be infinite or unbounded, there must be a test case for that.",
|
|
522
|
+
"- Every boundary, limit, and edge of the input domain must be exercised.",
|
|
523
|
+
"",
|
|
524
|
+
"If tests are missing or incomplete, write them.",
|
|
525
|
+
"Score 0–100 based on boundary-condition coverage AFTER your fixes.",
|
|
526
|
+
"",
|
|
527
|
+
].join("\n"),
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
path: ".smithers/prompts/sweep-unit-tests.mdx",
|
|
531
|
+
contents: [
|
|
532
|
+
"Review unit test coverage for this feature group.",
|
|
533
|
+
"",
|
|
534
|
+
"For every feature listed:",
|
|
535
|
+
"- Verify unit tests exist covering every boundary condition, edge case, and error condition.",
|
|
536
|
+
"- Tests must exercise: empty inputs, null/undefined, maximum values, minimum values, off-by-one, type mismatches, concurrent access (if applicable), and error/exception paths.",
|
|
537
|
+
"- Each test should isolate a single behavior.",
|
|
538
|
+
"",
|
|
539
|
+
"If tests are missing or incomplete, write them.",
|
|
540
|
+
"Score 0–100 based on edge-case coverage AFTER your fixes.",
|
|
541
|
+
"",
|
|
542
|
+
].join("\n"),
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
path: ".smithers/prompts/sweep-observability.mdx",
|
|
546
|
+
contents: [
|
|
547
|
+
"Review observability coverage for this feature group.",
|
|
548
|
+
"",
|
|
549
|
+
"For every feature listed, verify the implementation has:",
|
|
550
|
+
"- Structured logging at appropriate levels (debug, info, warn, error).",
|
|
551
|
+
"- Distributed tracing spans with meaningful names and attributes.",
|
|
552
|
+
"- Prometheus metrics where applicable (counters, histograms, gauges).",
|
|
553
|
+
"- Error logging with sufficient context for debugging.",
|
|
554
|
+
"",
|
|
555
|
+
"If observability is missing or insufficient, add it.",
|
|
556
|
+
"Score 0–100 based on observability completeness AFTER your fixes.",
|
|
557
|
+
"",
|
|
558
|
+
].join("\n"),
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
path: ".smithers/prompts/sweep-implementation.mdx",
|
|
562
|
+
contents: [
|
|
563
|
+
"Review implementation quality for this feature group.",
|
|
564
|
+
"",
|
|
565
|
+
"For every feature listed, verify the implementation:",
|
|
566
|
+
"- Has complete and accurate JSDoc on all public functions, types, and classes.",
|
|
567
|
+
"- Is clean, production-ready code.",
|
|
568
|
+
"- Prefers inlining over abstraction — only abstract if the pattern is used more than once.",
|
|
569
|
+
"- Has ZERO magic strings or magic numbers — all such values must be named constants.",
|
|
570
|
+
"- Has no dead code, unused imports, or commented-out code.",
|
|
571
|
+
"",
|
|
572
|
+
"If you see any way to improve the code, improve it.",
|
|
573
|
+
"Score 0–100 based on code quality AFTER your fixes.",
|
|
574
|
+
"",
|
|
575
|
+
].join("\n"),
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
path: ".smithers/prompts/sweep-cli.mdx",
|
|
579
|
+
contents: [
|
|
580
|
+
"Review CLI coverage for this feature group.",
|
|
581
|
+
"",
|
|
582
|
+
"For every feature listed:",
|
|
583
|
+
"- Determine if the feature should be accessible via the CLI.",
|
|
584
|
+
"- If it should, verify a CLI command or flag exists to use it.",
|
|
585
|
+
"- Verify the CLI help text is accurate and complete.",
|
|
586
|
+
"- If CLI access is missing and the feature warrants it, add it.",
|
|
587
|
+
"",
|
|
588
|
+
"Not every feature needs CLI access — use your judgment on applicability.",
|
|
589
|
+
"Score 0–100 based on CLI coverage of applicable features AFTER your fixes.",
|
|
590
|
+
"",
|
|
591
|
+
].join("\n"),
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
path: ".smithers/prompts/sync-features-scan.mdx",
|
|
595
|
+
contents: [
|
|
596
|
+
"# Feature Inventory Scan",
|
|
597
|
+
"",
|
|
598
|
+
"Produce an exhaustive feature inventory for this Smithers orchestrator codebase.",
|
|
599
|
+
"You have the full file tree below — DO NOT read any files yourself. Analyze the structure",
|
|
600
|
+
"and produce the feature groups directly.",
|
|
601
|
+
"",
|
|
602
|
+
"Rules:",
|
|
603
|
+
"1. Group features by domain using SCREAMING_SNAKE_CASE group names.",
|
|
604
|
+
"2. Use SCREAMING_SNAKE_CASE feature names that are specific and code-backed.",
|
|
605
|
+
"3. Split broad buckets into concrete, independently auditable features.",
|
|
606
|
+
'4. lastCommitHash should be "{props.currentHead}".',
|
|
607
|
+
"5. Include a markdownBody that explains the grouping.",
|
|
608
|
+
"",
|
|
609
|
+
"CODEBASE STRUCTURE:",
|
|
610
|
+
"{props.codebaseSummary}",
|
|
611
|
+
"",
|
|
612
|
+
"REQUIRED OUTPUT:",
|
|
613
|
+
"{props.schema}",
|
|
614
|
+
"",
|
|
615
|
+
].join("\n"),
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
path: ".smithers/prompts/sync-features-refine.mdx",
|
|
619
|
+
contents: [
|
|
620
|
+
"# Feature Inventory Refine",
|
|
621
|
+
"",
|
|
622
|
+
"Refine the existing feature inventory by analyzing recent changes.",
|
|
623
|
+
"",
|
|
624
|
+
'{props.lastCommitHash ? `Compare with the previous inventory at commit ${props.lastCommitHash}. Check what changed.` : "This is the first delta check — review the existing features against the codebase structure below."}',
|
|
625
|
+
"",
|
|
626
|
+
"CURRENT FEATURE GROUPS:",
|
|
627
|
+
"{JSON.stringify(props.existingFeatures ?? {}, null, 2)}",
|
|
628
|
+
"",
|
|
629
|
+
"CODEBASE STRUCTURE:",
|
|
630
|
+
"{props.codebaseSummary}",
|
|
631
|
+
"",
|
|
632
|
+
"Checklist:",
|
|
633
|
+
"1. Add any concrete missing features based on the codebase structure.",
|
|
634
|
+
"2. Remove entries not supported by the current codebase.",
|
|
635
|
+
"3. Preserve naming discipline: SCREAMING_SNAKE_CASE groups and features.",
|
|
636
|
+
'4. lastCommitHash should be "{props.currentHead}".',
|
|
637
|
+
"5. markdownBody should summarize what changed.",
|
|
638
|
+
"",
|
|
639
|
+
"REQUIRED OUTPUT:",
|
|
640
|
+
"{props.schema}",
|
|
641
|
+
"",
|
|
642
|
+
].join("\n"),
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
path: ".smithers/prompts/sync-features-write.mdx",
|
|
646
|
+
contents: [
|
|
647
|
+
"# Write Features File",
|
|
648
|
+
"",
|
|
649
|
+
"Write the file .smithers/specs/features.ts with the feature groups below.",
|
|
650
|
+
"",
|
|
651
|
+
"First create the directory: mkdir -p specs",
|
|
652
|
+
"",
|
|
653
|
+
"Use this exact TypeScript structure:",
|
|
654
|
+
"",
|
|
655
|
+
"```typescript",
|
|
656
|
+
"/**",
|
|
657
|
+
" * Code-backed Smithers feature inventory.",
|
|
658
|
+
" *",
|
|
659
|
+
" * Auto-generated by the sync-features workflow.",
|
|
660
|
+
' * Last synced at commit: {props.lastCommitHash ?? "unknown"}',
|
|
661
|
+
" */",
|
|
662
|
+
"",
|
|
663
|
+
"export const FeatureGroups = {'{'}",
|
|
664
|
+
" // Source: {'<'}file paths{'>'}",
|
|
665
|
+
" GROUP_NAME: [",
|
|
666
|
+
' "FEATURE_1",',
|
|
667
|
+
' "FEATURE_2",',
|
|
668
|
+
" ],",
|
|
669
|
+
"{'}'} as const satisfies Record{'<'}string, readonly string[]{'>'};",
|
|
670
|
+
"",
|
|
671
|
+
"type FeatureGroupMap = typeof FeatureGroups;",
|
|
672
|
+
"export type FeatureGroupName = keyof FeatureGroupMap;",
|
|
673
|
+
"export type FeatureName = FeatureGroupMap[FeatureGroupName][number];",
|
|
674
|
+
"",
|
|
675
|
+
"const featureEntries: Array{'<'}readonly [FeatureName, FeatureName]{'>'} = [];",
|
|
676
|
+
"",
|
|
677
|
+
"for (const group of Object.values(FeatureGroups) as readonly (readonly FeatureName[])[]) {'{'}",
|
|
678
|
+
" for (const feature of group) {'{'}",
|
|
679
|
+
" featureEntries.push([feature, feature] as const);",
|
|
680
|
+
" {'}'}",
|
|
681
|
+
"{'}'}",
|
|
682
|
+
"",
|
|
683
|
+
"export const Features = Object.freeze(",
|
|
684
|
+
" Object.fromEntries(featureEntries) as Record{'<'}FeatureName, FeatureName{'>'},{'\\n'}",
|
|
685
|
+
");",
|
|
686
|
+
"```",
|
|
687
|
+
"",
|
|
688
|
+
"FEATURE GROUPS:",
|
|
689
|
+
"{JSON.stringify(props.featureGroups, null, 2)}",
|
|
690
|
+
"",
|
|
691
|
+
'Write the file, then report: filePath ".smithers/specs/features.ts", commitHash "{props.lastCommitHash ?? "unknown"}", totalGroups and totalFeatures counts.',
|
|
692
|
+
"",
|
|
693
|
+
"REQUIRED OUTPUT:",
|
|
694
|
+
"{props.schema}",
|
|
695
|
+
"",
|
|
696
|
+
].join("\n"),
|
|
697
|
+
},
|
|
698
|
+
];
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* @returns {TemplateFile[]}
|
|
702
|
+
*/
|
|
703
|
+
function renderComponents() {
|
|
704
|
+
return [
|
|
705
|
+
{
|
|
706
|
+
path: ".smithers/components/Review.tsx",
|
|
707
|
+
contents: [
|
|
708
|
+
"// smithers-source: seeded",
|
|
709
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
710
|
+
'import { Parallel, Task, type AgentLike } from "smithers-orchestrator";',
|
|
711
|
+
'import { z } from "zod/v4";',
|
|
712
|
+
'import ReviewPrompt from "../prompts/review.mdx";',
|
|
713
|
+
"",
|
|
714
|
+
"const reviewIssueSchema = z.object({",
|
|
715
|
+
' severity: z.enum(["critical", "major", "minor", "nit"]),',
|
|
716
|
+
" title: z.string(),",
|
|
717
|
+
" file: z.string().nullable().default(null),",
|
|
718
|
+
" description: z.string(),",
|
|
719
|
+
"});",
|
|
720
|
+
"",
|
|
721
|
+
"export const reviewOutputSchema = z.object({",
|
|
722
|
+
" reviewer: z.string(),",
|
|
723
|
+
" approved: z.boolean(),",
|
|
724
|
+
" feedback: z.string(),",
|
|
725
|
+
" issues: z.array(reviewIssueSchema).default([]),",
|
|
726
|
+
"});",
|
|
727
|
+
"",
|
|
728
|
+
"type ReviewProps = {",
|
|
729
|
+
" idPrefix: string;",
|
|
730
|
+
" prompt: unknown;",
|
|
731
|
+
" agents: AgentLike[];",
|
|
732
|
+
"};",
|
|
733
|
+
"",
|
|
734
|
+
"export function Review({ idPrefix, prompt, agents }: ReviewProps) {",
|
|
735
|
+
' const promptText = typeof prompt === "string" ? prompt : JSON.stringify(prompt ?? null);',
|
|
736
|
+
" return (",
|
|
737
|
+
" <Parallel>",
|
|
738
|
+
" {agents.map((agent, index) => (",
|
|
739
|
+
" <Task",
|
|
740
|
+
" key={`${idPrefix}:${index}`}",
|
|
741
|
+
" id={`${idPrefix}:${index}`}",
|
|
742
|
+
" output={reviewOutputSchema}",
|
|
743
|
+
" agent={agent}",
|
|
744
|
+
" continueOnFail",
|
|
745
|
+
" >",
|
|
746
|
+
' <ReviewPrompt reviewer={`reviewer-${index + 1}`} prompt={promptText} />',
|
|
747
|
+
" </Task>",
|
|
748
|
+
" ))}",
|
|
749
|
+
" </Parallel>",
|
|
750
|
+
" );",
|
|
751
|
+
"}",
|
|
752
|
+
"",
|
|
753
|
+
].join("\n"),
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
path: ".smithers/components/ValidationLoop.tsx",
|
|
757
|
+
contents: [
|
|
758
|
+
"// smithers-source: seeded",
|
|
759
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
760
|
+
'import { Sequence, Loop, Task, type AgentLike } from "smithers-orchestrator";',
|
|
761
|
+
'import { z } from "zod/v4";',
|
|
762
|
+
'import { Review } from "~/components/Review";',
|
|
763
|
+
'import ImplementPrompt from "~/prompts/implement.mdx";',
|
|
764
|
+
'import ValidatePrompt from "~/prompts/validate.mdx";',
|
|
765
|
+
"",
|
|
766
|
+
"export const implementOutputSchema = z.object({",
|
|
767
|
+
" summary: z.string(),",
|
|
768
|
+
" filesChanged: z.array(z.string()).default([]),",
|
|
769
|
+
" allTestsPassing: z.boolean().default(true),",
|
|
770
|
+
"});",
|
|
771
|
+
"export const validateOutputSchema = z.object({",
|
|
772
|
+
" summary: z.string(),",
|
|
773
|
+
" allPassed: z.boolean().default(true),",
|
|
774
|
+
" failingSummary: z.string().nullable().default(null),",
|
|
775
|
+
"});",
|
|
776
|
+
"",
|
|
777
|
+
"export type ValidationLoopProps = {",
|
|
778
|
+
" idPrefix: string;",
|
|
779
|
+
" prompt: unknown;",
|
|
780
|
+
" implementAgents: AgentLike[];",
|
|
781
|
+
" reviewAgents: AgentLike[];",
|
|
782
|
+
" validateAgents?: AgentLike[];",
|
|
783
|
+
" feedback?: string | null;",
|
|
784
|
+
" done?: boolean;",
|
|
785
|
+
" maxIterations?: number;",
|
|
786
|
+
"};",
|
|
787
|
+
"",
|
|
788
|
+
"export function ValidationLoop({",
|
|
789
|
+
" idPrefix,",
|
|
790
|
+
" prompt,",
|
|
791
|
+
" implementAgents,",
|
|
792
|
+
" reviewAgents,",
|
|
793
|
+
" validateAgents,",
|
|
794
|
+
" feedback,",
|
|
795
|
+
" done = false,",
|
|
796
|
+
" maxIterations = 3,",
|
|
797
|
+
"}: ValidationLoopProps) {",
|
|
798
|
+
' const promptText = typeof prompt === "string" ? prompt : JSON.stringify(prompt ?? null);',
|
|
799
|
+
" return (",
|
|
800
|
+
' <Loop id={`${idPrefix}:loop`} until={done} maxIterations={maxIterations} onMaxReached="return-last">',
|
|
801
|
+
" <Sequence>",
|
|
802
|
+
" <Task id={`${idPrefix}:implement`} output={implementOutputSchema} agent={implementAgents} timeoutMs={1_800_000} heartbeatTimeoutMs={600_000}>",
|
|
803
|
+
" <ImplementPrompt prompt={feedback",
|
|
804
|
+
" ? `${promptText}\\n\\n---\\nPREVIOUS ATTEMPT FEEDBACK (fix these issues):\\n${feedback}`",
|
|
805
|
+
" : promptText} />",
|
|
806
|
+
" </Task>",
|
|
807
|
+
" <Task id={`${idPrefix}:validate`} output={validateOutputSchema} agent={validateAgents && validateAgents.length > 0",
|
|
808
|
+
" ? validateAgents",
|
|
809
|
+
" : implementAgents} timeoutMs={1_800_000} heartbeatTimeoutMs={600_000}>",
|
|
810
|
+
" <ValidatePrompt prompt={promptText} />",
|
|
811
|
+
" </Task>",
|
|
812
|
+
" <Review idPrefix={`${idPrefix}:review`} prompt={promptText} agents={reviewAgents} />",
|
|
813
|
+
" </Sequence>",
|
|
814
|
+
" </Loop>",
|
|
815
|
+
" );",
|
|
816
|
+
"}",
|
|
817
|
+
"",
|
|
818
|
+
].join("\n"),
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
path: ".smithers/components/CommandProbe.tsx",
|
|
822
|
+
contents: [
|
|
823
|
+
"// smithers-source: seeded",
|
|
824
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
825
|
+
'import { Task } from "smithers-orchestrator";',
|
|
826
|
+
'import { z } from "zod/v4";',
|
|
827
|
+
"",
|
|
828
|
+
"export const commandProbeOutputSchema = z.looseObject({",
|
|
829
|
+
" command: z.string(),",
|
|
830
|
+
" available: z.boolean(),",
|
|
831
|
+
"});",
|
|
832
|
+
"",
|
|
833
|
+
"export function CommandProbe({ id, command }: { id: string; command: string }) {",
|
|
834
|
+
" return (",
|
|
835
|
+
" <Task id={id} output={commandProbeOutputSchema}>",
|
|
836
|
+
" {{ command, available: true }}",
|
|
837
|
+
" </Task>",
|
|
838
|
+
" );",
|
|
839
|
+
"}",
|
|
840
|
+
"",
|
|
841
|
+
].join("\n"),
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
path: ".smithers/components/GrillMe.tsx",
|
|
845
|
+
contents: [
|
|
846
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
847
|
+
'import { Loop, Sequence, Task, type AgentLike, type OutputTarget } from "smithers-orchestrator";',
|
|
848
|
+
'import { z } from "zod/v4";',
|
|
849
|
+
'import GrillMeSkill from "skills/grill-me/SKILL.md";',
|
|
850
|
+
'import AskUserInstructions from "../prompts/ask-user-instructions.mdx";',
|
|
851
|
+
"",
|
|
852
|
+
"export const grillOutputSchema = z.looseObject({",
|
|
853
|
+
" question: z.string(),",
|
|
854
|
+
" recommendedAnswer: z.string().nullable().default(null),",
|
|
855
|
+
" branch: z.string().nullable().default(null),",
|
|
856
|
+
" resolved: z.boolean().default(false),",
|
|
857
|
+
" questionsAsked: z.number().int().default(0),",
|
|
858
|
+
" sharedUnderstanding: z.string().nullable().default(null),",
|
|
859
|
+
"});",
|
|
860
|
+
"",
|
|
861
|
+
"type GrillMeProps = {",
|
|
862
|
+
" idPrefix: string;",
|
|
863
|
+
" context: string;",
|
|
864
|
+
" currentDraft?: any;",
|
|
865
|
+
" agent: AgentLike | AgentLike[];",
|
|
866
|
+
" output: OutputTarget;",
|
|
867
|
+
" maxIterations?: number;",
|
|
868
|
+
" children?: React.ReactNode;",
|
|
869
|
+
" until?: boolean;",
|
|
870
|
+
"};",
|
|
871
|
+
"",
|
|
872
|
+
"export function GrillMe({",
|
|
873
|
+
" idPrefix,",
|
|
874
|
+
" context,",
|
|
875
|
+
" currentDraft,",
|
|
876
|
+
" agent,",
|
|
877
|
+
" output,",
|
|
878
|
+
" maxIterations = 1,",
|
|
879
|
+
" children,",
|
|
880
|
+
" until = false,",
|
|
881
|
+
"}: GrillMeProps) {",
|
|
882
|
+
" return (",
|
|
883
|
+
" <Sequence>",
|
|
884
|
+
" <Loop until={until} maxIterations={maxIterations}>",
|
|
885
|
+
" <Task id={`${idPrefix}:grill`} output={output} agent={agent}>",
|
|
886
|
+
" <GrillMeSkill />",
|
|
887
|
+
" <AskUserInstructions />",
|
|
888
|
+
" {context}",
|
|
889
|
+
" {currentDraft && `",
|
|
890
|
+
"",
|
|
891
|
+
"## Current Progress",
|
|
892
|
+
"Here is the result of the previous iteration:",
|
|
893
|
+
"",
|
|
894
|
+
"\\`\\`\\`json",
|
|
895
|
+
"${JSON.stringify(currentDraft, null, 2)}",
|
|
896
|
+
"\\`\\`\\`",
|
|
897
|
+
"",
|
|
898
|
+
"Review this result. If it completely fulfills the requirements, you can stop asking questions and mark resolved: true. Otherwise, I want you to further grill me so we can improve it.`}",
|
|
899
|
+
" </Task>",
|
|
900
|
+
" {children}",
|
|
901
|
+
" </Loop>",
|
|
902
|
+
" </Sequence>",
|
|
903
|
+
" );",
|
|
904
|
+
"}",
|
|
905
|
+
"",
|
|
906
|
+
].join("\n"),
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
path: ".smithers/components/WriteAPrd.tsx",
|
|
910
|
+
contents: [
|
|
911
|
+
"// smithers-source: seeded",
|
|
912
|
+
"// Inspired by Matt Pocock's write-a-prd skill (https://github.com/mattpocock/skills)",
|
|
913
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
914
|
+
'import { Sequence, Task, type AgentLike } from "smithers-orchestrator";',
|
|
915
|
+
'import { z } from "zod/v4";',
|
|
916
|
+
'import WriteAPrdPrompt from "../prompts/write-a-prd.mdx";',
|
|
917
|
+
"",
|
|
918
|
+
"const userStorySchema = z.object({",
|
|
919
|
+
" actor: z.string(),",
|
|
920
|
+
" feature: z.string(),",
|
|
921
|
+
" benefit: z.string(),",
|
|
922
|
+
"});",
|
|
923
|
+
"",
|
|
924
|
+
"const moduleSketchSchema = z.object({",
|
|
925
|
+
" name: z.string(),",
|
|
926
|
+
" description: z.string(),",
|
|
927
|
+
" isDeepModule: z.boolean().default(false),",
|
|
928
|
+
" needsTests: z.boolean().default(false),",
|
|
929
|
+
"});",
|
|
930
|
+
"",
|
|
931
|
+
"export const prdOutputSchema = z.looseObject({",
|
|
932
|
+
" title: z.string(),",
|
|
933
|
+
" problemStatement: z.string(),",
|
|
934
|
+
" solution: z.string(),",
|
|
935
|
+
" userStories: z.array(userStorySchema).default([]),",
|
|
936
|
+
" implementationDecisions: z.array(z.string()).default([]),",
|
|
937
|
+
" testingDecisions: z.array(z.string()).default([]),",
|
|
938
|
+
" modules: z.array(moduleSketchSchema).default([]),",
|
|
939
|
+
" outOfScope: z.array(z.string()).default([]),",
|
|
940
|
+
" furtherNotes: z.string().nullable().default(null),",
|
|
941
|
+
" markdownBody: z.string(),",
|
|
942
|
+
"});",
|
|
943
|
+
"",
|
|
944
|
+
"type WriteAPrdProps = {",
|
|
945
|
+
" idPrefix: string;",
|
|
946
|
+
" context: string;",
|
|
947
|
+
" agent: AgentLike | AgentLike[];",
|
|
948
|
+
" exploreCodebase?: boolean;",
|
|
949
|
+
" interviewFirst?: boolean;",
|
|
950
|
+
" maxInterviewQuestions?: number;",
|
|
951
|
+
" moduleDepthGuidance?: boolean;",
|
|
952
|
+
" outputFormat?: \"markdown\" | \"json\";",
|
|
953
|
+
"};",
|
|
954
|
+
"",
|
|
955
|
+
"export function WriteAPrd({",
|
|
956
|
+
" idPrefix,",
|
|
957
|
+
" context,",
|
|
958
|
+
" agent,",
|
|
959
|
+
" exploreCodebase = true,",
|
|
960
|
+
" interviewFirst = true,",
|
|
961
|
+
" maxInterviewQuestions = 20,",
|
|
962
|
+
" moduleDepthGuidance = true,",
|
|
963
|
+
' outputFormat = "markdown",',
|
|
964
|
+
"}: WriteAPrdProps) {",
|
|
965
|
+
" const additionalInstructions = [",
|
|
966
|
+
" exploreCodebase && \"Explore the codebase to verify assertions before writing.\",",
|
|
967
|
+
" interviewFirst && `Interview the user first (up to ${maxInterviewQuestions} questions).`,",
|
|
968
|
+
" moduleDepthGuidance && \"Prefer deep modules over shallow ones when sketching architecture.\",",
|
|
969
|
+
" outputFormat === \"json\" && \"Return structured JSON in addition to the markdown body.\",",
|
|
970
|
+
" ].filter(Boolean).join(\"\\n\");",
|
|
971
|
+
"",
|
|
972
|
+
" return (",
|
|
973
|
+
" <Sequence>",
|
|
974
|
+
" <Task",
|
|
975
|
+
" id={`${idPrefix}:prd`}",
|
|
976
|
+
" output={prdOutputSchema}",
|
|
977
|
+
" agent={agent}",
|
|
978
|
+
" >",
|
|
979
|
+
" <WriteAPrdPrompt context={context} additionalInstructions={additionalInstructions} />",
|
|
980
|
+
" </Task>",
|
|
981
|
+
" </Sequence>",
|
|
982
|
+
" );",
|
|
983
|
+
"}",
|
|
984
|
+
"",
|
|
985
|
+
].join("\n"),
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
path: ".smithers/components/ForEachFeature.tsx",
|
|
989
|
+
contents: [
|
|
990
|
+
"// smithers-source: seeded",
|
|
991
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
992
|
+
'import { Parallel, Sequence, Task, type AgentLike } from "smithers-orchestrator";',
|
|
993
|
+
'import { z } from "zod/v4";',
|
|
994
|
+
'import FeatureTaskPrompt from "~/prompts/feature-task.mdx";',
|
|
995
|
+
"",
|
|
996
|
+
"export const forEachFeatureResultSchema = z.looseObject({",
|
|
997
|
+
" groupName: z.string(),",
|
|
998
|
+
" result: z.string(),",
|
|
999
|
+
" featuresCovered: z.array(z.string()).default([]),",
|
|
1000
|
+
" score: z.number().min(0).max(100).optional(),",
|
|
1001
|
+
"});",
|
|
1002
|
+
"",
|
|
1003
|
+
"export const forEachFeatureMergeSchema = z.looseObject({",
|
|
1004
|
+
" totalGroups: z.number().int(),",
|
|
1005
|
+
" summary: z.string(),",
|
|
1006
|
+
" mergedResult: z.string(),",
|
|
1007
|
+
" markdownBody: z.string(),",
|
|
1008
|
+
"});",
|
|
1009
|
+
"",
|
|
1010
|
+
"type ForEachFeatureProps = {",
|
|
1011
|
+
" idPrefix: string;",
|
|
1012
|
+
" agent: AgentLike | AgentLike[];",
|
|
1013
|
+
" features: Record<string, string[]>;",
|
|
1014
|
+
" prompt: React.ReactNode;",
|
|
1015
|
+
" maxConcurrency?: number;",
|
|
1016
|
+
" mergeAgent?: AgentLike | AgentLike[];",
|
|
1017
|
+
" granularity?: \"group\" | \"feature\";",
|
|
1018
|
+
"};",
|
|
1019
|
+
"",
|
|
1020
|
+
"type FeatureWorkItem = {",
|
|
1021
|
+
" id: string;",
|
|
1022
|
+
" groupName: string;",
|
|
1023
|
+
" features: string[];",
|
|
1024
|
+
"};",
|
|
1025
|
+
"",
|
|
1026
|
+
"function slugifyFeatureToken(value: string) {",
|
|
1027
|
+
" const normalized = value",
|
|
1028
|
+
' .toLowerCase()',
|
|
1029
|
+
' .replace(/[^a-z0-9]+/g, "-")',
|
|
1030
|
+
' .replace(/^-+|-+$/g, "");',
|
|
1031
|
+
' return normalized.length > 0 ? normalized : "item";',
|
|
1032
|
+
"}",
|
|
1033
|
+
"",
|
|
1034
|
+
"export function ForEachFeature({",
|
|
1035
|
+
" idPrefix,",
|
|
1036
|
+
" agent,",
|
|
1037
|
+
" features,",
|
|
1038
|
+
" prompt,",
|
|
1039
|
+
" maxConcurrency = 5,",
|
|
1040
|
+
" mergeAgent,",
|
|
1041
|
+
' granularity = "group",',
|
|
1042
|
+
"}: ForEachFeatureProps) {",
|
|
1043
|
+
" const featureEntries = Object.entries(features ?? {}).filter(([, groupFeatures]) => Array.isArray(groupFeatures) && groupFeatures.length > 0);",
|
|
1044
|
+
" const workItems: FeatureWorkItem[] = granularity === \"feature\"",
|
|
1045
|
+
" ? featureEntries.flatMap(([groupName, groupFeatures]) =>",
|
|
1046
|
+
" groupFeatures.map((feature, index) => ({",
|
|
1047
|
+
" id: `${slugifyFeatureToken(groupName)}:${slugifyFeatureToken(feature)}:${index}`,",
|
|
1048
|
+
" groupName,",
|
|
1049
|
+
" features: [feature],",
|
|
1050
|
+
" })),",
|
|
1051
|
+
" )",
|
|
1052
|
+
" : featureEntries.map(([groupName, groupFeatures], index) => ({",
|
|
1053
|
+
" id: `${slugifyFeatureToken(groupName)}:${index}`,",
|
|
1054
|
+
" groupName,",
|
|
1055
|
+
" features: groupFeatures,",
|
|
1056
|
+
" }));",
|
|
1057
|
+
"",
|
|
1058
|
+
" const mergeNeeds: Record<string, string> = Object.fromEntries(",
|
|
1059
|
+
" workItems.map((item, index) => [`item${index}`, `${idPrefix}:group:${item.id}`]),",
|
|
1060
|
+
" );",
|
|
1061
|
+
" const mergeDeps = Object.fromEntries(",
|
|
1062
|
+
" workItems.map((_, index) => [`item${index}`, forEachFeatureResultSchema]),",
|
|
1063
|
+
" ) as Record<string, typeof forEachFeatureResultSchema>;",
|
|
1064
|
+
"",
|
|
1065
|
+
" if (workItems.length === 0) {",
|
|
1066
|
+
" return (",
|
|
1067
|
+
" <Sequence>",
|
|
1068
|
+
" <Task id={`${idPrefix}:merge`} output={forEachFeatureMergeSchema}>",
|
|
1069
|
+
" {{",
|
|
1070
|
+
" totalGroups: 0,",
|
|
1071
|
+
' summary: "No feature groups were provided.",',
|
|
1072
|
+
' mergedResult: "",',
|
|
1073
|
+
' markdownBody: "# Feature Audit\\n\\nNo feature groups were provided.",',
|
|
1074
|
+
" }}",
|
|
1075
|
+
" </Task>",
|
|
1076
|
+
" </Sequence>",
|
|
1077
|
+
" );",
|
|
1078
|
+
" }",
|
|
1079
|
+
"",
|
|
1080
|
+
" return (",
|
|
1081
|
+
" <Sequence>",
|
|
1082
|
+
" <Parallel maxConcurrency={maxConcurrency}>",
|
|
1083
|
+
" {workItems.map((item) => (",
|
|
1084
|
+
" <Task",
|
|
1085
|
+
" key={`${idPrefix}:${item.id}`}",
|
|
1086
|
+
" id={`${idPrefix}:group:${item.id}`}",
|
|
1087
|
+
" output={forEachFeatureResultSchema}",
|
|
1088
|
+
" agent={agent}",
|
|
1089
|
+
" continueOnFail",
|
|
1090
|
+
" >",
|
|
1091
|
+
" <FeatureTaskPrompt",
|
|
1092
|
+
" granularity={granularity}",
|
|
1093
|
+
" groupName={item.groupName}",
|
|
1094
|
+
" features={item.features}",
|
|
1095
|
+
" prompt={prompt}",
|
|
1096
|
+
" />",
|
|
1097
|
+
" </Task>",
|
|
1098
|
+
" ))}",
|
|
1099
|
+
" </Parallel>",
|
|
1100
|
+
" <Task",
|
|
1101
|
+
" id={`${idPrefix}:merge`}",
|
|
1102
|
+
" output={forEachFeatureMergeSchema}",
|
|
1103
|
+
" agent={mergeAgent ?? agent}",
|
|
1104
|
+
" needs={mergeNeeds}",
|
|
1105
|
+
" deps={mergeDeps}",
|
|
1106
|
+
" >",
|
|
1107
|
+
" {(deps) => {",
|
|
1108
|
+
" const results = workItems.map((_, index) => deps[`item${index}`]);",
|
|
1109
|
+
" const totalGroups = new Set(workItems.map((item) => item.groupName)).size;",
|
|
1110
|
+
" return [",
|
|
1111
|
+
' "# Merge Feature Results",',
|
|
1112
|
+
' "",',
|
|
1113
|
+
' `Granularity: ${granularity}`,',
|
|
1114
|
+
' `Distinct groups: ${totalGroups}`,',
|
|
1115
|
+
' `Work items: ${workItems.length}`,',
|
|
1116
|
+
' `Set totalGroups to ${totalGroups}.`,',
|
|
1117
|
+
' "",',
|
|
1118
|
+
' "Combine the per-group results below into a single coherent output.",',
|
|
1119
|
+
' "Preserve group boundaries, highlight the most important gaps, and produce a markdownBody suitable for a report.",',
|
|
1120
|
+
' "",',
|
|
1121
|
+
" ...results.flatMap((result, index) => {",
|
|
1122
|
+
" const groupLabel = workItems[index]?.groupName ?? `Group ${index + 1}`;",
|
|
1123
|
+
" return [",
|
|
1124
|
+
" `## ${groupLabel}`,",
|
|
1125
|
+
" `Features covered: ${(result?.featuresCovered ?? []).join(\", \") || \"none\"}`,",
|
|
1126
|
+
" result?.score != null ? `Score: ${result.score}` : null,",
|
|
1127
|
+
" result?.result ?? \"\",",
|
|
1128
|
+
" \"\",",
|
|
1129
|
+
" ].filter(Boolean);",
|
|
1130
|
+
" }),",
|
|
1131
|
+
" ].join(\"\\n\");",
|
|
1132
|
+
" }}",
|
|
1133
|
+
" </Task>",
|
|
1134
|
+
" </Sequence>",
|
|
1135
|
+
" );",
|
|
1136
|
+
"}",
|
|
1137
|
+
"",
|
|
1138
|
+
].join("\n"),
|
|
1139
|
+
},
|
|
1140
|
+
{
|
|
1141
|
+
path: ".smithers/components/FeatureEnum.tsx",
|
|
1142
|
+
contents: [
|
|
1143
|
+
"// smithers-source: seeded",
|
|
1144
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
1145
|
+
'import { Sequence, Task, type AgentLike } from "smithers-orchestrator";',
|
|
1146
|
+
'import { z } from "zod/v4";',
|
|
1147
|
+
'import FeatureEnumScanPrompt from "../prompts/feature-enum-scan.mdx";',
|
|
1148
|
+
'import FeatureEnumRefinePrompt from "../prompts/feature-enum-refine.mdx";',
|
|
1149
|
+
"",
|
|
1150
|
+
"export const featureEnumOutputSchema = z.looseObject({",
|
|
1151
|
+
" featureGroups: z.record(z.string(), z.array(z.string())).default({}),",
|
|
1152
|
+
" totalFeatures: z.number().int().default(0),",
|
|
1153
|
+
" lastCommitHash: z.string().nullable().optional(),",
|
|
1154
|
+
" markdownBody: z.string(),",
|
|
1155
|
+
"});",
|
|
1156
|
+
"",
|
|
1157
|
+
"type FeatureEnumProps = {",
|
|
1158
|
+
" idPrefix: string;",
|
|
1159
|
+
" agent: AgentLike | AgentLike[];",
|
|
1160
|
+
" refineIterations?: number;",
|
|
1161
|
+
" existingFeatures?: Record<string, string[]> | null;",
|
|
1162
|
+
" lastCommitHash?: string | null;",
|
|
1163
|
+
" additionalContext?: string;",
|
|
1164
|
+
"};",
|
|
1165
|
+
"",
|
|
1166
|
+
'const memoryNamespace = { kind: "workflow", id: "feature-enum" } as const;',
|
|
1167
|
+
"",
|
|
1168
|
+
"export function FeatureEnum({",
|
|
1169
|
+
" idPrefix,",
|
|
1170
|
+
" agent,",
|
|
1171
|
+
" refineIterations,",
|
|
1172
|
+
" existingFeatures = null,",
|
|
1173
|
+
" lastCommitHash = null,",
|
|
1174
|
+
' additionalContext = "",',
|
|
1175
|
+
"}: FeatureEnumProps) {",
|
|
1176
|
+
" const isFirstRun = !existingFeatures;",
|
|
1177
|
+
" const totalRefineIterations = Math.max(1, refineIterations ?? (isFirstRun ? 5 : 1));",
|
|
1178
|
+
" const scanTaskId = `${idPrefix}:scan`;",
|
|
1179
|
+
" const refineTaskIds = Array.from({ length: totalRefineIterations }, (_, index) => `${idPrefix}:refine:${index + 1}`);",
|
|
1180
|
+
" const finalTaskId = `${idPrefix}:result`;",
|
|
1181
|
+
"",
|
|
1182
|
+
" return (",
|
|
1183
|
+
" <Sequence>",
|
|
1184
|
+
" {isFirstRun && (",
|
|
1185
|
+
" <Task",
|
|
1186
|
+
" id={scanTaskId}",
|
|
1187
|
+
" output={featureEnumOutputSchema}",
|
|
1188
|
+
" agent={agent}",
|
|
1189
|
+
" memory={{",
|
|
1190
|
+
" remember: {",
|
|
1191
|
+
" namespace: memoryNamespace,",
|
|
1192
|
+
" key: scanTaskId,",
|
|
1193
|
+
" },",
|
|
1194
|
+
" }}",
|
|
1195
|
+
" >",
|
|
1196
|
+
" <FeatureEnumScanPrompt additionalContext={additionalContext} />",
|
|
1197
|
+
" </Task>",
|
|
1198
|
+
" )}",
|
|
1199
|
+
"",
|
|
1200
|
+
" {refineTaskIds.map((taskId, index) => {",
|
|
1201
|
+
" const previousTaskId = index === 0",
|
|
1202
|
+
" ? (isFirstRun ? scanTaskId : null)",
|
|
1203
|
+
" : refineTaskIds[index - 1];",
|
|
1204
|
+
"",
|
|
1205
|
+
" if (previousTaskId) {",
|
|
1206
|
+
" return (",
|
|
1207
|
+
" <Task",
|
|
1208
|
+
" key={taskId}",
|
|
1209
|
+
" id={taskId}",
|
|
1210
|
+
" output={featureEnumOutputSchema}",
|
|
1211
|
+
" agent={agent}",
|
|
1212
|
+
" needs={{ previous: previousTaskId }}",
|
|
1213
|
+
" deps={{ previous: featureEnumOutputSchema }}",
|
|
1214
|
+
" memory={{",
|
|
1215
|
+
" recall: {",
|
|
1216
|
+
" namespace: memoryNamespace,",
|
|
1217
|
+
' query: "feature inventory feature enum grouped features",',
|
|
1218
|
+
" topK: 5,",
|
|
1219
|
+
" },",
|
|
1220
|
+
" remember: {",
|
|
1221
|
+
" namespace: memoryNamespace,",
|
|
1222
|
+
" key: taskId,",
|
|
1223
|
+
" },",
|
|
1224
|
+
" }}",
|
|
1225
|
+
" >",
|
|
1226
|
+
" {(deps) => (",
|
|
1227
|
+
" <FeatureEnumRefinePrompt",
|
|
1228
|
+
" existingFeatures={deps.previous.featureGroups}",
|
|
1229
|
+
" lastCommitHash={deps.previous.lastCommitHash ?? lastCommitHash}",
|
|
1230
|
+
" iteration={index + 1}",
|
|
1231
|
+
" />",
|
|
1232
|
+
" )}",
|
|
1233
|
+
" </Task>",
|
|
1234
|
+
" );",
|
|
1235
|
+
" }",
|
|
1236
|
+
"",
|
|
1237
|
+
" return (",
|
|
1238
|
+
" <Task",
|
|
1239
|
+
" key={taskId}",
|
|
1240
|
+
" id={taskId}",
|
|
1241
|
+
" output={featureEnumOutputSchema}",
|
|
1242
|
+
" agent={agent}",
|
|
1243
|
+
" memory={{",
|
|
1244
|
+
" recall: {",
|
|
1245
|
+
" namespace: memoryNamespace,",
|
|
1246
|
+
' query: "feature inventory feature enum grouped features",',
|
|
1247
|
+
" topK: 5,",
|
|
1248
|
+
" },",
|
|
1249
|
+
" remember: {",
|
|
1250
|
+
" namespace: memoryNamespace,",
|
|
1251
|
+
" key: taskId,",
|
|
1252
|
+
" },",
|
|
1253
|
+
" }}",
|
|
1254
|
+
" >",
|
|
1255
|
+
" <FeatureEnumRefinePrompt",
|
|
1256
|
+
" existingFeatures={existingFeatures ?? {}}",
|
|
1257
|
+
" lastCommitHash={lastCommitHash}",
|
|
1258
|
+
" iteration={index + 1}",
|
|
1259
|
+
" />",
|
|
1260
|
+
" </Task>",
|
|
1261
|
+
" );",
|
|
1262
|
+
" })}",
|
|
1263
|
+
"",
|
|
1264
|
+
" <Task",
|
|
1265
|
+
" id={finalTaskId}",
|
|
1266
|
+
" output={featureEnumOutputSchema}",
|
|
1267
|
+
" needs={{ final: refineTaskIds[refineTaskIds.length - 1] ?? scanTaskId }}",
|
|
1268
|
+
" deps={{ final: featureEnumOutputSchema }}",
|
|
1269
|
+
" >",
|
|
1270
|
+
" {(deps) => deps.final}",
|
|
1271
|
+
" </Task>",
|
|
1272
|
+
" </Sequence>",
|
|
1273
|
+
" );",
|
|
1274
|
+
"}",
|
|
1275
|
+
"",
|
|
1276
|
+
].join("\n"),
|
|
1277
|
+
},
|
|
1278
|
+
];
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* @param {string} id
|
|
1282
|
+
* @param {string} displayName
|
|
1283
|
+
* @param {string[]} body
|
|
1284
|
+
*/
|
|
1285
|
+
function renderWorkflowFile(id, displayName, body) {
|
|
1286
|
+
return {
|
|
1287
|
+
path: `.smithers/workflows/${id}.tsx`,
|
|
1288
|
+
contents: [
|
|
1289
|
+
"// smithers-source: seeded",
|
|
1290
|
+
`// smithers-display-name: ${displayName}`,
|
|
1291
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
1292
|
+
...body,
|
|
1293
|
+
"",
|
|
1294
|
+
].join("\n"),
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* @returns {TemplateFile[]}
|
|
1299
|
+
*/
|
|
1300
|
+
function renderWorkflows() {
|
|
1301
|
+
const sharedImports = [
|
|
1302
|
+
'import { createSmithers } from "smithers-orchestrator";',
|
|
1303
|
+
'import { z } from "zod/v4";',
|
|
1304
|
+
'import { agents } from "../agents";',
|
|
1305
|
+
];
|
|
1306
|
+
return [
|
|
1307
|
+
renderWorkflowFile("implement", "Implement", [
|
|
1308
|
+
...sharedImports,
|
|
1309
|
+
'import { ValidationLoop, implementOutputSchema, validateOutputSchema } from "../components/ValidationLoop";',
|
|
1310
|
+
'import { reviewOutputSchema } from "../components/Review";',
|
|
1311
|
+
"",
|
|
1312
|
+
"const { Workflow, smithers } = createSmithers({",
|
|
1313
|
+
" implement: implementOutputSchema,",
|
|
1314
|
+
" validate: validateOutputSchema,",
|
|
1315
|
+
" review: reviewOutputSchema,",
|
|
1316
|
+
"});",
|
|
1317
|
+
"",
|
|
1318
|
+
"export default smithers((ctx) => {",
|
|
1319
|
+
' const validate = ctx.outputMaybe("validate", { nodeId: "impl:validate" });',
|
|
1320
|
+
" const reviews = ctx.outputs.review ?? [];",
|
|
1321
|
+
"",
|
|
1322
|
+
" // done = false until validate has actually run AND passed, AND at least one reviewer approved",
|
|
1323
|
+
" const hasValidated = validate !== undefined;",
|
|
1324
|
+
" const validationPassed = hasValidated && validate.allPassed !== false;",
|
|
1325
|
+
" const anyApproved = reviews.length > 0 && reviews.some((r: any) => r.approved === true);",
|
|
1326
|
+
" const done = validationPassed && anyApproved;",
|
|
1327
|
+
"",
|
|
1328
|
+
" const feedbackParts: string[] = [];",
|
|
1329
|
+
" if (validate && !validationPassed && validate.failingSummary) {",
|
|
1330
|
+
" feedbackParts.push(`VALIDATION FAILED:\\n${validate.failingSummary}`);",
|
|
1331
|
+
" }",
|
|
1332
|
+
" for (const review of reviews) {",
|
|
1333
|
+
" if (review.approved === false) {",
|
|
1334
|
+
" feedbackParts.push(`REVIEWER REJECTED:\\n${review.feedback}`);",
|
|
1335
|
+
" if (review.issues?.length) {",
|
|
1336
|
+
" for (const issue of review.issues) {",
|
|
1337
|
+
" feedbackParts.push(` [${issue.severity}] ${issue.title}: ${issue.description}${issue.file ? ` (${issue.file})` : \"\"}`);",
|
|
1338
|
+
" }",
|
|
1339
|
+
" }",
|
|
1340
|
+
" }",
|
|
1341
|
+
" }",
|
|
1342
|
+
" const feedback = feedbackParts.length > 0 ? feedbackParts.join(\"\\n\\n\") : null;",
|
|
1343
|
+
"",
|
|
1344
|
+
" return (",
|
|
1345
|
+
' <Workflow name="implement">',
|
|
1346
|
+
" <ValidationLoop",
|
|
1347
|
+
' idPrefix="impl"',
|
|
1348
|
+
" prompt={ctx.input.prompt}",
|
|
1349
|
+
" implementAgents={agents.smart}",
|
|
1350
|
+
" validateAgents={agents.cheapFast}",
|
|
1351
|
+
" reviewAgents={agents.smart}",
|
|
1352
|
+
" feedback={feedback}",
|
|
1353
|
+
" done={done}",
|
|
1354
|
+
" maxIterations={3}",
|
|
1355
|
+
" />",
|
|
1356
|
+
" </Workflow>",
|
|
1357
|
+
" );",
|
|
1358
|
+
"});",
|
|
1359
|
+
]),
|
|
1360
|
+
renderWorkflowFile("research-plan-implement", "Research Plan Implement", [
|
|
1361
|
+
...sharedImports,
|
|
1362
|
+
'import { ValidationLoop, implementOutputSchema, validateOutputSchema } from "../components/ValidationLoop";',
|
|
1363
|
+
'import { reviewOutputSchema } from "../components/Review";',
|
|
1364
|
+
'import ResearchPrompt from "../prompts/research.mdx";',
|
|
1365
|
+
'import PlanPrompt from "../prompts/plan.mdx";',
|
|
1366
|
+
"",
|
|
1367
|
+
"const researchOutputSchema = z.looseObject({",
|
|
1368
|
+
" summary: z.string(),",
|
|
1369
|
+
" keyFindings: z.array(z.string()).default([]),",
|
|
1370
|
+
"});",
|
|
1371
|
+
"",
|
|
1372
|
+
"const planOutputSchema = z.looseObject({",
|
|
1373
|
+
" summary: z.string(),",
|
|
1374
|
+
" steps: z.array(z.string()).default([]),",
|
|
1375
|
+
"});",
|
|
1376
|
+
"",
|
|
1377
|
+
"const inputSchema = z.object({",
|
|
1378
|
+
' prompt: z.string().default("Implement the requested change."),',
|
|
1379
|
+
" tdd: z.boolean().default(false),",
|
|
1380
|
+
"});",
|
|
1381
|
+
"",
|
|
1382
|
+
"const { Workflow, Task, Sequence, smithers } = createSmithers({",
|
|
1383
|
+
" input: inputSchema,",
|
|
1384
|
+
" research: researchOutputSchema,",
|
|
1385
|
+
" plan: planOutputSchema,",
|
|
1386
|
+
" implement: implementOutputSchema,",
|
|
1387
|
+
" validate: validateOutputSchema,",
|
|
1388
|
+
" review: reviewOutputSchema,",
|
|
1389
|
+
"});",
|
|
1390
|
+
"",
|
|
1391
|
+
"export default smithers((ctx) => {",
|
|
1392
|
+
" const prompt = ctx.input.prompt;",
|
|
1393
|
+
" const tdd = ctx.input.tdd;",
|
|
1394
|
+
"",
|
|
1395
|
+
' const research = ctx.outputMaybe("research", { nodeId: "research" });',
|
|
1396
|
+
' const plan = ctx.outputMaybe("plan", { nodeId: "plan" });',
|
|
1397
|
+
"",
|
|
1398
|
+
" // Enrich plan prompt with research findings",
|
|
1399
|
+
" const planPromptParts = [",
|
|
1400
|
+
" prompt,",
|
|
1401
|
+
" research",
|
|
1402
|
+
" ? `RESEARCH FINDINGS:\\n${research.summary}\\n\\nKey findings:\\n${research.keyFindings.map((f: string) => `- ${f}`).join(\"\\n\")}`",
|
|
1403
|
+
" : null,",
|
|
1404
|
+
" tdd",
|
|
1405
|
+
' ? "IMPORTANT: Write tests FIRST. The plan MUST start with test steps before any implementation steps. Follow test-driven development: define expected behavior in tests, then implement to make them pass."',
|
|
1406
|
+
" : null,",
|
|
1407
|
+
" ];",
|
|
1408
|
+
' const planPrompt = planPromptParts.filter(Boolean).join("\\n\\n---\\n");',
|
|
1409
|
+
"",
|
|
1410
|
+
" // Enrich implement prompt with both research and plan",
|
|
1411
|
+
" const implementPrompt = [",
|
|
1412
|
+
" prompt,",
|
|
1413
|
+
" research ? `RESEARCH FINDINGS:\\n${research.summary}\\n\\nKey findings:\\n${research.keyFindings.map((f: string) => `- ${f}`).join(\"\\n\")}` : null,",
|
|
1414
|
+
" plan ? `IMPLEMENTATION PLAN:\\n${plan.summary}\\n\\nSteps:\\n${plan.steps.map((s: string, i: number) => `${i + 1}. ${s}`).join(\"\\n\")}` : null,",
|
|
1415
|
+
' tdd ? "IMPORTANT: Follow the plan\'s test-first approach. Write or update tests before implementing production code." : null,',
|
|
1416
|
+
" ].filter(Boolean).join(\"\\n\\n---\\n\");",
|
|
1417
|
+
"",
|
|
1418
|
+
" // Validation loop feedback",
|
|
1419
|
+
' const validate = ctx.outputMaybe("validate", { nodeId: "impl:validate" });',
|
|
1420
|
+
" const reviews = ctx.outputs.review ?? [];",
|
|
1421
|
+
"",
|
|
1422
|
+
" const hasValidated = validate !== undefined;",
|
|
1423
|
+
" const validationPassed = hasValidated && validate.allPassed !== false;",
|
|
1424
|
+
" const anyApproved = reviews.length > 0 && reviews.some((r: any) => r.approved === true);",
|
|
1425
|
+
" const done = validationPassed && anyApproved;",
|
|
1426
|
+
"",
|
|
1427
|
+
" const feedbackParts: string[] = [];",
|
|
1428
|
+
" if (validate && !validationPassed && validate.failingSummary) {",
|
|
1429
|
+
" feedbackParts.push(`VALIDATION FAILED:\\n${validate.failingSummary}`);",
|
|
1430
|
+
" }",
|
|
1431
|
+
" for (const review of reviews) {",
|
|
1432
|
+
" if (review.approved === false) {",
|
|
1433
|
+
" feedbackParts.push(`REVIEWER REJECTED:\\n${review.feedback}`);",
|
|
1434
|
+
" if (review.issues?.length) {",
|
|
1435
|
+
" for (const issue of review.issues) {",
|
|
1436
|
+
" feedbackParts.push(` [${issue.severity}] ${issue.title}: ${issue.description}${issue.file ? ` (${issue.file})` : \"\"}`);",
|
|
1437
|
+
" }",
|
|
1438
|
+
" }",
|
|
1439
|
+
" }",
|
|
1440
|
+
" }",
|
|
1441
|
+
" const feedback = feedbackParts.length > 0 ? feedbackParts.join(\"\\n\\n\") : null;",
|
|
1442
|
+
"",
|
|
1443
|
+
" return (",
|
|
1444
|
+
' <Workflow name="research-plan-implement">',
|
|
1445
|
+
" <Sequence>",
|
|
1446
|
+
' <Task id="research" output={researchOutputSchema} agent={agents.smartTool}>',
|
|
1447
|
+
" <ResearchPrompt prompt={prompt} />",
|
|
1448
|
+
" </Task>",
|
|
1449
|
+
' <Task id="plan" output={planOutputSchema} agent={agents.smart}>',
|
|
1450
|
+
" <PlanPrompt prompt={planPrompt} />",
|
|
1451
|
+
" </Task>",
|
|
1452
|
+
" <ValidationLoop",
|
|
1453
|
+
' idPrefix="impl"',
|
|
1454
|
+
" prompt={implementPrompt}",
|
|
1455
|
+
" implementAgents={agents.smart}",
|
|
1456
|
+
" validateAgents={agents.cheapFast}",
|
|
1457
|
+
" reviewAgents={agents.smart}",
|
|
1458
|
+
" feedback={feedback}",
|
|
1459
|
+
" done={done}",
|
|
1460
|
+
" maxIterations={3}",
|
|
1461
|
+
" />",
|
|
1462
|
+
" </Sequence>",
|
|
1463
|
+
" </Workflow>",
|
|
1464
|
+
" );",
|
|
1465
|
+
"});",
|
|
1466
|
+
]),
|
|
1467
|
+
renderWorkflowFile("review", "Review", [
|
|
1468
|
+
...sharedImports,
|
|
1469
|
+
'import { Review, reviewOutputSchema } from "../components/Review";',
|
|
1470
|
+
"",
|
|
1471
|
+
"const inputSchema = z.object({",
|
|
1472
|
+
' prompt: z.string().default("Review the current repository changes."),',
|
|
1473
|
+
"});",
|
|
1474
|
+
"",
|
|
1475
|
+
"const { Workflow, smithers } = createSmithers({",
|
|
1476
|
+
" input: inputSchema,",
|
|
1477
|
+
" review: reviewOutputSchema,",
|
|
1478
|
+
"});",
|
|
1479
|
+
"",
|
|
1480
|
+
"export default smithers((ctx) => (",
|
|
1481
|
+
' <Workflow name="review">',
|
|
1482
|
+
" <Review",
|
|
1483
|
+
' idPrefix="review"',
|
|
1484
|
+
" prompt={ctx.input.prompt}",
|
|
1485
|
+
" agents={agents.smart}",
|
|
1486
|
+
" />",
|
|
1487
|
+
" </Workflow>",
|
|
1488
|
+
"));",
|
|
1489
|
+
]),
|
|
1490
|
+
renderWorkflowFile("plan", "Plan", [
|
|
1491
|
+
...sharedImports,
|
|
1492
|
+
'import PlanPrompt from "../prompts/plan.mdx";',
|
|
1493
|
+
"",
|
|
1494
|
+
"const planOutputSchema = z.looseObject({",
|
|
1495
|
+
" summary: z.string(),",
|
|
1496
|
+
" steps: z.array(z.string()).default([]),",
|
|
1497
|
+
"});",
|
|
1498
|
+
"",
|
|
1499
|
+
"const inputSchema = z.object({",
|
|
1500
|
+
' prompt: z.string().default("Create an implementation plan."),',
|
|
1501
|
+
"});",
|
|
1502
|
+
"",
|
|
1503
|
+
"const { Workflow, Task, smithers } = createSmithers({",
|
|
1504
|
+
" input: inputSchema,",
|
|
1505
|
+
" plan: planOutputSchema,",
|
|
1506
|
+
"});",
|
|
1507
|
+
"",
|
|
1508
|
+
"export default smithers((ctx) => (",
|
|
1509
|
+
' <Workflow name="plan">',
|
|
1510
|
+
' <Task id="plan" output={planOutputSchema} agent={agents.smart}>',
|
|
1511
|
+
" <PlanPrompt prompt={ctx.input.prompt} />",
|
|
1512
|
+
" </Task>",
|
|
1513
|
+
" </Workflow>",
|
|
1514
|
+
"));",
|
|
1515
|
+
]),
|
|
1516
|
+
renderWorkflowFile("research", "Research", [
|
|
1517
|
+
...sharedImports,
|
|
1518
|
+
'import ResearchPrompt from "../prompts/research.mdx";',
|
|
1519
|
+
"",
|
|
1520
|
+
"const researchOutputSchema = z.looseObject({",
|
|
1521
|
+
" summary: z.string(),",
|
|
1522
|
+
" keyFindings: z.array(z.string()).default([]),",
|
|
1523
|
+
"});",
|
|
1524
|
+
"",
|
|
1525
|
+
"const inputSchema = z.object({",
|
|
1526
|
+
' prompt: z.string().default("Research the given topic."),',
|
|
1527
|
+
"});",
|
|
1528
|
+
"",
|
|
1529
|
+
"const { Workflow, Task, smithers } = createSmithers({",
|
|
1530
|
+
" input: inputSchema,",
|
|
1531
|
+
" research: researchOutputSchema,",
|
|
1532
|
+
"});",
|
|
1533
|
+
"",
|
|
1534
|
+
"export default smithers((ctx) => (",
|
|
1535
|
+
' <Workflow name="research">',
|
|
1536
|
+
' <Task id="research" output={researchOutputSchema} agent={agents.smartTool}>',
|
|
1537
|
+
" <ResearchPrompt prompt={ctx.input.prompt} />",
|
|
1538
|
+
" </Task>",
|
|
1539
|
+
" </Workflow>",
|
|
1540
|
+
"));",
|
|
1541
|
+
]),
|
|
1542
|
+
renderWorkflowFile("ticket-create", "Ticket Create", [
|
|
1543
|
+
...sharedImports,
|
|
1544
|
+
'import TicketPrompt from "../prompts/ticket.mdx";',
|
|
1545
|
+
"",
|
|
1546
|
+
"const ticketCreateOutputSchema = z.looseObject({",
|
|
1547
|
+
" title: z.string(),",
|
|
1548
|
+
" description: z.string(),",
|
|
1549
|
+
" acceptanceCriteria: z.array(z.string()).default([]),",
|
|
1550
|
+
"});",
|
|
1551
|
+
"",
|
|
1552
|
+
"const inputSchema = z.object({",
|
|
1553
|
+
' prompt: z.string().default("Create a ticket for the requested work."),',
|
|
1554
|
+
"});",
|
|
1555
|
+
"",
|
|
1556
|
+
"const { Workflow, Task, smithers } = createSmithers({",
|
|
1557
|
+
" input: inputSchema,",
|
|
1558
|
+
" ticket: ticketCreateOutputSchema,",
|
|
1559
|
+
"});",
|
|
1560
|
+
"",
|
|
1561
|
+
"export default smithers((ctx) => (",
|
|
1562
|
+
' <Workflow name="ticket-create">',
|
|
1563
|
+
' <Task id="ticket" output={ticketCreateOutputSchema} agent={agents.smart}>',
|
|
1564
|
+
" <TicketPrompt prompt={ctx.input.prompt} />",
|
|
1565
|
+
" </Task>",
|
|
1566
|
+
" </Workflow>",
|
|
1567
|
+
"));",
|
|
1568
|
+
]),
|
|
1569
|
+
renderWorkflowFile("tickets-create", "Tickets Create", [
|
|
1570
|
+
...sharedImports,
|
|
1571
|
+
'import TicketsCreatePrompt from "../prompts/tickets-create.mdx";',
|
|
1572
|
+
"",
|
|
1573
|
+
"const ticketsCreateOutputSchema = z.looseObject({",
|
|
1574
|
+
" summary: z.string(),",
|
|
1575
|
+
" tickets: z.array(z.object({",
|
|
1576
|
+
" title: z.string(),",
|
|
1577
|
+
" description: z.string(),",
|
|
1578
|
+
" acceptanceCriteria: z.array(z.string()).default([]),",
|
|
1579
|
+
" })).default([]),",
|
|
1580
|
+
"});",
|
|
1581
|
+
"",
|
|
1582
|
+
"const inputSchema = z.object({",
|
|
1583
|
+
' prompt: z.string().default("Create tickets for the requested work."),',
|
|
1584
|
+
"});",
|
|
1585
|
+
"",
|
|
1586
|
+
"const { Workflow, Task, smithers } = createSmithers({",
|
|
1587
|
+
" input: inputSchema,",
|
|
1588
|
+
" tickets: ticketsCreateOutputSchema,",
|
|
1589
|
+
"});",
|
|
1590
|
+
"",
|
|
1591
|
+
"export default smithers((ctx) => (",
|
|
1592
|
+
' <Workflow name="tickets-create">',
|
|
1593
|
+
' <Task id="tickets" output={ticketsCreateOutputSchema} agent={agents.smart}>',
|
|
1594
|
+
" <TicketsCreatePrompt prompt={ctx.input.prompt} />",
|
|
1595
|
+
" </Task>",
|
|
1596
|
+
" </Workflow>",
|
|
1597
|
+
"));",
|
|
1598
|
+
]),
|
|
1599
|
+
renderWorkflowFile("ralph", "Ralph", [
|
|
1600
|
+
...sharedImports,
|
|
1601
|
+
"",
|
|
1602
|
+
"const ralphOutputSchema = z.looseObject({",
|
|
1603
|
+
" summary: z.string(),",
|
|
1604
|
+
"});",
|
|
1605
|
+
"",
|
|
1606
|
+
"const inputSchema = z.object({",
|
|
1607
|
+
' prompt: z.string().default("Continue working on the current task."),',
|
|
1608
|
+
"});",
|
|
1609
|
+
"",
|
|
1610
|
+
"const { Workflow, Task, Loop, smithers } = createSmithers({",
|
|
1611
|
+
" input: inputSchema,",
|
|
1612
|
+
" ralph: ralphOutputSchema,",
|
|
1613
|
+
"});",
|
|
1614
|
+
"",
|
|
1615
|
+
"export default smithers((ctx) => (",
|
|
1616
|
+
' <Workflow name="ralph">',
|
|
1617
|
+
" <Loop until={false} maxIterations={Infinity}>",
|
|
1618
|
+
' <Task id="ralph" output={ralphOutputSchema} agent={agents.smart}>',
|
|
1619
|
+
" {ctx.input.prompt}",
|
|
1620
|
+
" </Task>",
|
|
1621
|
+
" </Loop>",
|
|
1622
|
+
" </Workflow>",
|
|
1623
|
+
"));",
|
|
1624
|
+
]),
|
|
1625
|
+
renderWorkflowFile("improve-test-coverage", "Improve Test Coverage", [
|
|
1626
|
+
...sharedImports,
|
|
1627
|
+
'import { ValidationLoop, implementOutputSchema, validateOutputSchema } from "../components/ValidationLoop";',
|
|
1628
|
+
'import { reviewOutputSchema } from "../components/Review";',
|
|
1629
|
+
"",
|
|
1630
|
+
"const inputSchema = z.object({",
|
|
1631
|
+
' prompt: z.string().default("Improve the test coverage for the current repository."),',
|
|
1632
|
+
"});",
|
|
1633
|
+
"",
|
|
1634
|
+
"const { Workflow, smithers } = createSmithers({",
|
|
1635
|
+
" input: inputSchema,",
|
|
1636
|
+
" implement: implementOutputSchema,",
|
|
1637
|
+
" validate: validateOutputSchema,",
|
|
1638
|
+
" review: reviewOutputSchema,",
|
|
1639
|
+
"});",
|
|
1640
|
+
"",
|
|
1641
|
+
"export default smithers((ctx) => (",
|
|
1642
|
+
' <Workflow name="improve-test-coverage">',
|
|
1643
|
+
" <ValidationLoop",
|
|
1644
|
+
' idPrefix="improve-test-coverage"',
|
|
1645
|
+
" prompt={ctx.input.prompt}",
|
|
1646
|
+
" implementAgents={agents.smart}",
|
|
1647
|
+
" validateAgents={agents.cheapFast}",
|
|
1648
|
+
" reviewAgents={agents.smart}",
|
|
1649
|
+
" />",
|
|
1650
|
+
" </Workflow>",
|
|
1651
|
+
"));",
|
|
1652
|
+
]),
|
|
1653
|
+
renderWorkflowFile("debug", "Debug", [
|
|
1654
|
+
...sharedImports,
|
|
1655
|
+
'import { ValidationLoop, implementOutputSchema, validateOutputSchema } from "../components/ValidationLoop";',
|
|
1656
|
+
'import { reviewOutputSchema } from "../components/Review";',
|
|
1657
|
+
"",
|
|
1658
|
+
"const inputSchema = z.object({",
|
|
1659
|
+
' prompt: z.string().default("Reproduce and fix the reported bug."),',
|
|
1660
|
+
"});",
|
|
1661
|
+
"",
|
|
1662
|
+
"const { Workflow, smithers } = createSmithers({",
|
|
1663
|
+
" input: inputSchema,",
|
|
1664
|
+
" implement: implementOutputSchema,",
|
|
1665
|
+
" validate: validateOutputSchema,",
|
|
1666
|
+
" review: reviewOutputSchema,",
|
|
1667
|
+
"});",
|
|
1668
|
+
"",
|
|
1669
|
+
"export default smithers((ctx) => (",
|
|
1670
|
+
' <Workflow name="debug">',
|
|
1671
|
+
" <ValidationLoop",
|
|
1672
|
+
' idPrefix="debug"',
|
|
1673
|
+
" prompt={ctx.input.prompt}",
|
|
1674
|
+
" implementAgents={agents.smart}",
|
|
1675
|
+
" validateAgents={agents.cheapFast}",
|
|
1676
|
+
" reviewAgents={agents.smart}",
|
|
1677
|
+
" />",
|
|
1678
|
+
" </Workflow>",
|
|
1679
|
+
"));",
|
|
1680
|
+
]),
|
|
1681
|
+
renderWorkflowFile("grill-me", "Grill Me", [
|
|
1682
|
+
...sharedImports,
|
|
1683
|
+
'import { GrillMe, grillOutputSchema } from "../components/GrillMe";',
|
|
1684
|
+
"",
|
|
1685
|
+
'const WORKFLOW_ID = "grill-me";',
|
|
1686
|
+
"",
|
|
1687
|
+
"const { Workflow, smithers, outputs } = createSmithers({",
|
|
1688
|
+
" input: z.object({",
|
|
1689
|
+
' prompt: z.string().default("Describe what you want to get grilled on."),',
|
|
1690
|
+
" maxIterations: z.number().int().default(30),",
|
|
1691
|
+
" }),",
|
|
1692
|
+
" grill: grillOutputSchema,",
|
|
1693
|
+
"});",
|
|
1694
|
+
"",
|
|
1695
|
+
"export default smithers((ctx) => (",
|
|
1696
|
+
" <Workflow name={WORKFLOW_ID}>",
|
|
1697
|
+
" <GrillMe",
|
|
1698
|
+
" idPrefix={WORKFLOW_ID}",
|
|
1699
|
+
" context={ctx.input.prompt}",
|
|
1700
|
+
" agent={agents.smart}",
|
|
1701
|
+
" output={outputs.grill}",
|
|
1702
|
+
" maxIterations={ctx.input.maxIterations}",
|
|
1703
|
+
" />",
|
|
1704
|
+
" </Workflow>",
|
|
1705
|
+
"));",
|
|
1706
|
+
]),
|
|
1707
|
+
renderWorkflowFile("write-a-prd", "Write a PRD", [
|
|
1708
|
+
"// Inspired by Matt Pocock's write-a-prd skill (https://github.com/mattpocock/skills)",
|
|
1709
|
+
...sharedImports,
|
|
1710
|
+
'import { GrillMe, grillOutputSchema } from "../components/GrillMe";',
|
|
1711
|
+
'import WriteAPrdPrompt from "../prompts/write-a-prd.mdx";',
|
|
1712
|
+
"",
|
|
1713
|
+
'const WORKFLOW_ID = "write-a-prd";',
|
|
1714
|
+
"",
|
|
1715
|
+
"const { Workflow, Task, smithers, outputs } = createSmithers({",
|
|
1716
|
+
" input: z.object({",
|
|
1717
|
+
' prompt: z.string().default("Describe the feature or product you want to specify."),',
|
|
1718
|
+
" maxIterations: z.number().int().default(10),",
|
|
1719
|
+
" }),",
|
|
1720
|
+
" grill: grillOutputSchema,",
|
|
1721
|
+
" prd: z.looseObject({",
|
|
1722
|
+
" title: z.string(),",
|
|
1723
|
+
" problemStatement: z.string(),",
|
|
1724
|
+
" solution: z.string(),",
|
|
1725
|
+
" userStories: z.array(z.object({",
|
|
1726
|
+
" actor: z.string(),",
|
|
1727
|
+
" feature: z.string(),",
|
|
1728
|
+
" benefit: z.string(),",
|
|
1729
|
+
" })).default([]),",
|
|
1730
|
+
" implementationDecisions: z.array(z.string()).default([]),",
|
|
1731
|
+
" testingDecisions: z.array(z.string()).default([]),",
|
|
1732
|
+
" observabilityRequirements: z.array(z.string()).default([]),",
|
|
1733
|
+
" metrics: z.array(z.string()).default([]),",
|
|
1734
|
+
" verificationStrategy: z.array(z.string()).default([]),",
|
|
1735
|
+
" modules: z.array(z.object({",
|
|
1736
|
+
" name: z.string(),",
|
|
1737
|
+
" description: z.string(),",
|
|
1738
|
+
" isDeepModule: z.boolean().default(false),",
|
|
1739
|
+
" needsTests: z.boolean().default(false),",
|
|
1740
|
+
" })).default([]),",
|
|
1741
|
+
" outOfScope: z.array(z.string()).default([]),",
|
|
1742
|
+
" furtherNotes: z.string().nullable().default(null),",
|
|
1743
|
+
" markdownBody: z.string(),",
|
|
1744
|
+
" }),",
|
|
1745
|
+
"});",
|
|
1746
|
+
"",
|
|
1747
|
+
"export default smithers((ctx) => {",
|
|
1748
|
+
" const prdHistory = ctx.outputs.prd || [];",
|
|
1749
|
+
" const currentDraft = prdHistory[prdHistory.length - 1];",
|
|
1750
|
+
" const previousDraft = prdHistory[prdHistory.length - 2];",
|
|
1751
|
+
" const isUnchanged = prdHistory.length > 1 && JSON.stringify(currentDraft) === JSON.stringify(previousDraft);",
|
|
1752
|
+
"",
|
|
1753
|
+
" return (",
|
|
1754
|
+
" <Workflow name={WORKFLOW_ID}>",
|
|
1755
|
+
" <GrillMe",
|
|
1756
|
+
" until={isUnchanged}",
|
|
1757
|
+
" idPrefix={WORKFLOW_ID}",
|
|
1758
|
+
" context={ctx.input.prompt}",
|
|
1759
|
+
" currentDraft={currentDraft}",
|
|
1760
|
+
" agent={agents.smart}",
|
|
1761
|
+
" output={outputs.grill}",
|
|
1762
|
+
" maxIterations={ctx.input.maxIterations}",
|
|
1763
|
+
" >",
|
|
1764
|
+
" <Task id={`${WORKFLOW_ID}:prd`} output={outputs.prd} agent={agents.smart}>",
|
|
1765
|
+
" <WriteAPrdPrompt",
|
|
1766
|
+
" context={ctx.input.prompt}",
|
|
1767
|
+
" additionalInstructions={`Explore the codebase to verify assertions before writing.",
|
|
1768
|
+
"Prefer deep modules over shallow ones when sketching architecture.`}",
|
|
1769
|
+
" />",
|
|
1770
|
+
" </Task>",
|
|
1771
|
+
" </GrillMe>",
|
|
1772
|
+
" </Workflow>",
|
|
1773
|
+
" );",
|
|
1774
|
+
"});",
|
|
1775
|
+
]),
|
|
1776
|
+
renderWorkflowFile("feature-enum", "Feature Enum", [
|
|
1777
|
+
...sharedImports,
|
|
1778
|
+
'import { FeatureEnum, featureEnumOutputSchema } from "../components/FeatureEnum";',
|
|
1779
|
+
"",
|
|
1780
|
+
"const inputSchema = z.object({",
|
|
1781
|
+
" refineIterations: z.number().int().default(1),",
|
|
1782
|
+
" existingFeatures: z.record(z.string(), z.array(z.string())).nullable().default(null),",
|
|
1783
|
+
" lastCommitHash: z.string().nullable().default(null),",
|
|
1784
|
+
' additionalContext: z.string().default(""),',
|
|
1785
|
+
"});",
|
|
1786
|
+
"",
|
|
1787
|
+
"const { Workflow, smithers } = createSmithers({",
|
|
1788
|
+
" input: inputSchema,",
|
|
1789
|
+
" featureEnum: featureEnumOutputSchema,",
|
|
1790
|
+
"});",
|
|
1791
|
+
"",
|
|
1792
|
+
"export default smithers((ctx) => (",
|
|
1793
|
+
' <Workflow name="feature-enum">',
|
|
1794
|
+
" <FeatureEnum",
|
|
1795
|
+
' idPrefix="feature-enum"',
|
|
1796
|
+
" agent={agents.smartTool}",
|
|
1797
|
+
" refineIterations={ctx.input.refineIterations}",
|
|
1798
|
+
" existingFeatures={ctx.input.existingFeatures}",
|
|
1799
|
+
" lastCommitHash={ctx.input.lastCommitHash}",
|
|
1800
|
+
" additionalContext={ctx.input.additionalContext}",
|
|
1801
|
+
" />",
|
|
1802
|
+
" </Workflow>",
|
|
1803
|
+
"));",
|
|
1804
|
+
]),
|
|
1805
|
+
renderWorkflowFile("audit", "Audit", [
|
|
1806
|
+
...sharedImports,
|
|
1807
|
+
'import { ForEachFeature, forEachFeatureMergeSchema, forEachFeatureResultSchema } from "../components/ForEachFeature";',
|
|
1808
|
+
'import AuditPrompt from "../prompts/audit.mdx";',
|
|
1809
|
+
"",
|
|
1810
|
+
"const inputSchema = z.object({",
|
|
1811
|
+
" features: z.record(z.string(), z.array(z.string())).default({}),",
|
|
1812
|
+
' focus: z.string().default("code review"),',
|
|
1813
|
+
" additionalContext: z.string().nullable().default(null),",
|
|
1814
|
+
" maxConcurrency: z.number().int().default(5),",
|
|
1815
|
+
"});",
|
|
1816
|
+
"",
|
|
1817
|
+
"const { Workflow, smithers } = createSmithers({",
|
|
1818
|
+
" input: inputSchema,",
|
|
1819
|
+
" auditFeature: forEachFeatureResultSchema,",
|
|
1820
|
+
" audit: forEachFeatureMergeSchema,",
|
|
1821
|
+
"});",
|
|
1822
|
+
"",
|
|
1823
|
+
"export default smithers((ctx) => (",
|
|
1824
|
+
' <Workflow name="audit">',
|
|
1825
|
+
" <ForEachFeature",
|
|
1826
|
+
' idPrefix="audit"',
|
|
1827
|
+
" agent={agents.smart}",
|
|
1828
|
+
" features={ctx.input.features}",
|
|
1829
|
+
" prompt={<AuditPrompt focus={ctx.input.focus} additionalContext={ctx.input.additionalContext} />}",
|
|
1830
|
+
" maxConcurrency={ctx.input.maxConcurrency}",
|
|
1831
|
+
" mergeAgent={agents.smart}",
|
|
1832
|
+
" />",
|
|
1833
|
+
" </Workflow>",
|
|
1834
|
+
"));",
|
|
1835
|
+
]),
|
|
1836
|
+
{
|
|
1837
|
+
path: ".smithers/workflows/kanban.tsx",
|
|
1838
|
+
contents: [
|
|
1839
|
+
"// smithers-display-name: Kanban",
|
|
1840
|
+
"/** @jsxImportSource smithers-orchestrator */",
|
|
1841
|
+
'import { createSmithers, Sequence, Parallel, Worktree } from "smithers-orchestrator";',
|
|
1842
|
+
'import { readdirSync, readFileSync } from "node:fs";',
|
|
1843
|
+
'import { resolve } from "node:path";',
|
|
1844
|
+
'import { z } from "zod/v4";',
|
|
1845
|
+
'import { agents } from "../agents";',
|
|
1846
|
+
'import { ValidationLoop, implementOutputSchema, validateOutputSchema } from "../components/ValidationLoop";',
|
|
1847
|
+
'import { reviewOutputSchema } from "../components/Review";',
|
|
1848
|
+
'import MergeTicketsPrompt from "../prompts/merge-tickets.mdx";',
|
|
1849
|
+
"",
|
|
1850
|
+
"const ticketResultSchema = z.object({",
|
|
1851
|
+
" ticketId: z.string(),",
|
|
1852
|
+
" branch: z.string(),",
|
|
1853
|
+
' status: z.enum(["success", "partial", "failed"]),',
|
|
1854
|
+
" summary: z.string(),",
|
|
1855
|
+
"});",
|
|
1856
|
+
"",
|
|
1857
|
+
"const mergeResultSchema = z.object({",
|
|
1858
|
+
" merged: z.array(z.string()),",
|
|
1859
|
+
" conflicted: z.array(z.string()),",
|
|
1860
|
+
" summary: z.string(),",
|
|
1861
|
+
"});",
|
|
1862
|
+
"",
|
|
1863
|
+
"const { Workflow, Task, smithers, outputs } = createSmithers({",
|
|
1864
|
+
" implement: implementOutputSchema,",
|
|
1865
|
+
" validate: validateOutputSchema,",
|
|
1866
|
+
" review: reviewOutputSchema,",
|
|
1867
|
+
" ticketResult: ticketResultSchema,",
|
|
1868
|
+
" merge: mergeResultSchema,",
|
|
1869
|
+
"});",
|
|
1870
|
+
"",
|
|
1871
|
+
'function discoverTickets(): Array<{ id: string; slug: string; content: string }> {',
|
|
1872
|
+
' const ticketsDir = resolve(process.cwd(), ".smithers/tickets");',
|
|
1873
|
+
" try {",
|
|
1874
|
+
" return readdirSync(ticketsDir, { withFileTypes: true })",
|
|
1875
|
+
' .filter((e) => e.isFile() && e.name.endsWith(".md") && e.name !== ".gitkeep")',
|
|
1876
|
+
" .map((e) => {",
|
|
1877
|
+
' const content = readFileSync(resolve(ticketsDir, e.name), "utf8");',
|
|
1878
|
+
' const slug = e.name.replace(/\\.md$/, "");',
|
|
1879
|
+
" return { id: e.name, slug, content };",
|
|
1880
|
+
" })",
|
|
1881
|
+
" .sort((a, b) => a.id.localeCompare(b.id));",
|
|
1882
|
+
" } catch {",
|
|
1883
|
+
" return [];",
|
|
1884
|
+
" }",
|
|
1885
|
+
"}",
|
|
1886
|
+
"",
|
|
1887
|
+
"/** Build feedback string from validation + review outputs for a ticket. */",
|
|
1888
|
+
"function buildFeedback(",
|
|
1889
|
+
" ctx: any,",
|
|
1890
|
+
" slug: string,",
|
|
1891
|
+
"): { feedback: string | null; done: boolean } {",
|
|
1892
|
+
' const validate = ctx.outputMaybe("validate", { nodeId: `${slug}:validate` });',
|
|
1893
|
+
" const reviews = ctx.outputs.review ?? [];",
|
|
1894
|
+
"",
|
|
1895
|
+
" // Filter reviews for this ticket's prefix",
|
|
1896
|
+
" const ticketReviews = reviews.filter(",
|
|
1897
|
+
' (r: any) => r.reviewer?.startsWith?.("reviewer-"),',
|
|
1898
|
+
" );",
|
|
1899
|
+
"",
|
|
1900
|
+
" // done = false until validate has actually run AND passed, AND at least one reviewer approved",
|
|
1901
|
+
" const hasValidated = validate !== undefined;",
|
|
1902
|
+
" const validationPassed = hasValidated && validate.allPassed !== false;",
|
|
1903
|
+
" const anyReviewApproved = ticketReviews.length > 0 && ticketReviews.some((r: any) => r.approved === true);",
|
|
1904
|
+
" const done = validationPassed && anyReviewApproved;",
|
|
1905
|
+
"",
|
|
1906
|
+
" if (!hasValidated) return { feedback: null, done: false };",
|
|
1907
|
+
"",
|
|
1908
|
+
" const parts: string[] = [];",
|
|
1909
|
+
"",
|
|
1910
|
+
" if (!validationPassed && validate.failingSummary) {",
|
|
1911
|
+
" parts.push(`VALIDATION FAILED:\\n${validate.failingSummary}`);",
|
|
1912
|
+
" }",
|
|
1913
|
+
"",
|
|
1914
|
+
" for (const review of ticketReviews) {",
|
|
1915
|
+
" if (review.approved === false) {",
|
|
1916
|
+
" parts.push(`REVIEWER REJECTED:\\n${review.feedback}`);",
|
|
1917
|
+
" if (review.issues?.length) {",
|
|
1918
|
+
" for (const issue of review.issues) {",
|
|
1919
|
+
" parts.push(` [${issue.severity}] ${issue.title}: ${issue.description}${issue.file ? ` (${issue.file})` : \"\"}`);",
|
|
1920
|
+
" }",
|
|
1921
|
+
" }",
|
|
1922
|
+
" }",
|
|
1923
|
+
" }",
|
|
1924
|
+
"",
|
|
1925
|
+
" return {",
|
|
1926
|
+
' feedback: parts.length > 0 ? parts.join("\\n\\n") : null,',
|
|
1927
|
+
" done,",
|
|
1928
|
+
" };",
|
|
1929
|
+
"}",
|
|
1930
|
+
"",
|
|
1931
|
+
"export default smithers((ctx) => {",
|
|
1932
|
+
" const tickets = discoverTickets();",
|
|
1933
|
+
" const maxConcurrency = Number(ctx.input.maxConcurrency) || 3;",
|
|
1934
|
+
" const ticketResults = ctx.outputs.ticketResult ?? [];",
|
|
1935
|
+
"",
|
|
1936
|
+
" return (",
|
|
1937
|
+
' <Workflow name="kanban">',
|
|
1938
|
+
" <Sequence>",
|
|
1939
|
+
" {/* Implement each ticket in its own worktree branch, in parallel */}",
|
|
1940
|
+
" <Parallel maxConcurrency={maxConcurrency}>",
|
|
1941
|
+
" {tickets.map((ticket) => {",
|
|
1942
|
+
" const { feedback, done } = buildFeedback(ctx, ticket.slug);",
|
|
1943
|
+
" return (",
|
|
1944
|
+
" <Worktree",
|
|
1945
|
+
" key={ticket.slug}",
|
|
1946
|
+
" path={`.worktrees/${ticket.slug}`}",
|
|
1947
|
+
" branch={`ticket/${ticket.slug}`}",
|
|
1948
|
+
" >",
|
|
1949
|
+
" <Sequence>",
|
|
1950
|
+
" <ValidationLoop",
|
|
1951
|
+
" idPrefix={ticket.slug}",
|
|
1952
|
+
" prompt={`Implement the ticket below.\\n\\nTICKET FILE: .smithers/tickets/${ticket.id}\\n\\n${ticket.content}`}",
|
|
1953
|
+
" implementAgents={agents.smart}",
|
|
1954
|
+
" validateAgents={agents.smart}",
|
|
1955
|
+
" reviewAgents={agents.smart}",
|
|
1956
|
+
" feedback={feedback}",
|
|
1957
|
+
" done={done}",
|
|
1958
|
+
" maxIterations={3}",
|
|
1959
|
+
" />",
|
|
1960
|
+
" <Task",
|
|
1961
|
+
" id={`result-${ticket.slug}`}",
|
|
1962
|
+
" output={outputs.ticketResult}",
|
|
1963
|
+
" continueOnFail",
|
|
1964
|
+
" >",
|
|
1965
|
+
" {{",
|
|
1966
|
+
" ticketId: ticket.id,",
|
|
1967
|
+
" branch: `ticket/${ticket.slug}`,",
|
|
1968
|
+
' status: "success",',
|
|
1969
|
+
" summary: `Implemented ${ticket.slug}`,",
|
|
1970
|
+
" }}",
|
|
1971
|
+
" </Task>",
|
|
1972
|
+
" </Sequence>",
|
|
1973
|
+
" </Worktree>",
|
|
1974
|
+
" );",
|
|
1975
|
+
" })}",
|
|
1976
|
+
" </Parallel>",
|
|
1977
|
+
"",
|
|
1978
|
+
" {/* Agent merges completed branches back into main */}",
|
|
1979
|
+
' <Task id="merge" output={outputs.merge} agent={agents.smart}>',
|
|
1980
|
+
" <MergeTicketsPrompt ticketSummary={ticketResults",
|
|
1981
|
+
' .map((r) => `- ${r.ticketId}: branch "${r.branch}" — ${r.status} (${r.summary})`)',
|
|
1982
|
+
' .join("\\n")} />',
|
|
1983
|
+
" </Task>",
|
|
1984
|
+
" </Sequence>",
|
|
1985
|
+
" </Workflow>",
|
|
1986
|
+
" );",
|
|
1987
|
+
"});",
|
|
1988
|
+
"",
|
|
1989
|
+
].join("\n"),
|
|
1990
|
+
},
|
|
1991
|
+
];
|
|
1992
|
+
}
|
|
1993
|
+
/**
|
|
1994
|
+
* @param {DependencyVersions} versions
|
|
1995
|
+
* @param {NodeJS.ProcessEnv} env
|
|
1996
|
+
* @returns {TemplateFile[]}
|
|
1997
|
+
*/
|
|
1998
|
+
function renderTemplateFiles(versions, env) {
|
|
1999
|
+
return [
|
|
2000
|
+
{
|
|
2001
|
+
path: ".smithers/.gitignore",
|
|
2002
|
+
contents: ["node_modules/", "executions/", "runs/", "sandboxes/", "state/", "tmp/", "*.db", "*.sqlite", "dist/", ".DS_Store", ""].join("\n"),
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
path: ".smithers/package.json",
|
|
2006
|
+
contents: renderPackageJson(versions),
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
path: ".smithers/tsconfig.json",
|
|
2010
|
+
contents: renderTsconfig(),
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
path: ".smithers/bunfig.toml",
|
|
2014
|
+
contents: ['preload = ["./preload.ts"]', ""].join("\n"),
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
path: ".smithers/preload.ts",
|
|
2018
|
+
contents: ['import { mdxPlugin } from "smithers-orchestrator";', "", "mdxPlugin();", ""].join("\n"),
|
|
2019
|
+
},
|
|
2020
|
+
{
|
|
2021
|
+
path: ".smithers/agents.ts",
|
|
2022
|
+
contents: generateAgentsTs(env),
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
path: ".smithers/smithers.config.ts",
|
|
2026
|
+
contents: [
|
|
2027
|
+
"export const repoCommands = {",
|
|
2028
|
+
" lint: null,",
|
|
2029
|
+
" test: null,",
|
|
2030
|
+
" coverage: null,",
|
|
2031
|
+
"} as const;",
|
|
2032
|
+
"",
|
|
2033
|
+
"export default { repoCommands };",
|
|
2034
|
+
"",
|
|
2035
|
+
].join("\n"),
|
|
2036
|
+
},
|
|
2037
|
+
...renderPrompts(),
|
|
2038
|
+
...renderComponents(),
|
|
2039
|
+
...renderWorkflows(),
|
|
2040
|
+
{
|
|
2041
|
+
path: ".smithers/tickets/.gitkeep",
|
|
2042
|
+
contents: "",
|
|
2043
|
+
},
|
|
2044
|
+
];
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* @param {InitOptions} [options]
|
|
2048
|
+
* @returns {InitResult}
|
|
2049
|
+
*/
|
|
2050
|
+
export function initWorkflowPack(options = {}) {
|
|
2051
|
+
const rootDir = resolve(options.rootDir ?? process.cwd(), ".smithers");
|
|
2052
|
+
const writtenFiles = [];
|
|
2053
|
+
const skippedFiles = [];
|
|
2054
|
+
const preservedPaths = [];
|
|
2055
|
+
const versions = readDependencyVersions();
|
|
2056
|
+
const env = process.env;
|
|
2057
|
+
ensureDir(rootDir);
|
|
2058
|
+
ensureDir(resolve(rootDir, "prompts"));
|
|
2059
|
+
ensureDir(resolve(rootDir, "components"));
|
|
2060
|
+
ensureDir(resolve(rootDir, "workflows"));
|
|
2061
|
+
ensureDir(resolve(rootDir, "tickets"));
|
|
2062
|
+
const executionsDir = resolve(rootDir, "executions");
|
|
2063
|
+
if (existsSync(executionsDir)) {
|
|
2064
|
+
preservedPaths.push(executionsDir);
|
|
2065
|
+
}
|
|
2066
|
+
else {
|
|
2067
|
+
ensureDir(executionsDir);
|
|
2068
|
+
}
|
|
2069
|
+
for (const file of renderTemplateFiles(versions, env)) {
|
|
2070
|
+
const absolutePath = resolve(options.rootDir ?? process.cwd(), file.path);
|
|
2071
|
+
ensureParent(absolutePath);
|
|
2072
|
+
if (existsSync(absolutePath) && !options.force) {
|
|
2073
|
+
skippedFiles.push(absolutePath);
|
|
2074
|
+
continue;
|
|
2075
|
+
}
|
|
2076
|
+
writeFileSync(absolutePath, file.contents, "utf8");
|
|
2077
|
+
writtenFiles.push(absolutePath);
|
|
2078
|
+
}
|
|
2079
|
+
return {
|
|
2080
|
+
rootDir,
|
|
2081
|
+
writtenFiles,
|
|
2082
|
+
skippedFiles,
|
|
2083
|
+
preservedPaths,
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
const WORKFLOW_FOLLOW_UPS = {
|
|
2087
|
+
"research": [
|
|
2088
|
+
{ command: "workflow run write-a-prd", description: "Formalize findings into a PRD" },
|
|
2089
|
+
{ command: "workflow run plan", description: "Turn research into an implementation plan" },
|
|
2090
|
+
],
|
|
2091
|
+
"plan": [
|
|
2092
|
+
{ command: "workflow run research-plan-implement", description: "Research, plan, and execute" },
|
|
2093
|
+
{ command: "workflow run implement", description: "Execute the plan" },
|
|
2094
|
+
{ command: "workflow run tickets-create", description: "Break plan into tickets" },
|
|
2095
|
+
{ command: "workflow run grill-me", description: "Stress-test the plan first" },
|
|
2096
|
+
],
|
|
2097
|
+
"ticket-create": [
|
|
2098
|
+
{ command: "workflow run implement", description: "Implement the ticket" },
|
|
2099
|
+
{ command: "workflow run kanban", description: "Implement all tickets in parallel" },
|
|
2100
|
+
],
|
|
2101
|
+
"tickets-create": [
|
|
2102
|
+
{ command: "workflow run implement", description: "Implement a ticket" },
|
|
2103
|
+
{ command: "workflow run kanban", description: "Implement all tickets in parallel" },
|
|
2104
|
+
],
|
|
2105
|
+
"kanban": [
|
|
2106
|
+
{ command: "workflow run review", description: "Review the merged changes" },
|
|
2107
|
+
{ command: "workflow run improve-test-coverage", description: "Add tests for implemented tickets" },
|
|
2108
|
+
],
|
|
2109
|
+
"grill-me": [
|
|
2110
|
+
{ command: "workflow run write-a-prd", description: "Turn shared understanding into a PRD" },
|
|
2111
|
+
{ command: "workflow run tickets-create", description: "Break directly into tickets" },
|
|
2112
|
+
],
|
|
2113
|
+
"write-a-prd": [
|
|
2114
|
+
{ command: "workflow run tickets-create", description: "Break PRD into implementable tickets" },
|
|
2115
|
+
{ command: "workflow run plan", description: "Turn PRD into a phased plan" },
|
|
2116
|
+
{ command: "workflow run implement", description: "Start building from the PRD" },
|
|
2117
|
+
],
|
|
2118
|
+
"debug": [
|
|
2119
|
+
{ command: "workflow run review", description: "Review the fix" },
|
|
2120
|
+
{ command: "workflow run improve-test-coverage", description: "Add regression tests" },
|
|
2121
|
+
],
|
|
2122
|
+
"implement": [
|
|
2123
|
+
{ command: "workflow run review", description: "Review the changes" },
|
|
2124
|
+
{ command: "workflow run improve-test-coverage", description: "Improve test coverage" },
|
|
2125
|
+
],
|
|
2126
|
+
"research-plan-implement": [
|
|
2127
|
+
{ command: "workflow run review", description: "Review the changes" },
|
|
2128
|
+
{ command: "workflow run improve-test-coverage", description: "Improve test coverage" },
|
|
2129
|
+
],
|
|
2130
|
+
"review": [
|
|
2131
|
+
{ command: "workflow run implement", description: "Address review feedback" },
|
|
2132
|
+
],
|
|
2133
|
+
"feature-enum": [
|
|
2134
|
+
{ command: "workflow run audit", description: "Audit all features" },
|
|
2135
|
+
{ command: "workflow run implement", description: "Implement missing features" },
|
|
2136
|
+
],
|
|
2137
|
+
"audit": [
|
|
2138
|
+
{ command: "workflow run implement", description: "Address audit findings" },
|
|
2139
|
+
{ command: "workflow run tickets-create", description: "Create tickets from findings" },
|
|
2140
|
+
],
|
|
2141
|
+
};
|
|
2142
|
+
/**
|
|
2143
|
+
* @param {string} workflowPath
|
|
2144
|
+
* @returns {WorkflowCta[]}
|
|
2145
|
+
*/
|
|
2146
|
+
export function getWorkflowFollowUpCtas(workflowPath) {
|
|
2147
|
+
const id = workflowPath
|
|
2148
|
+
.replace(/^.*\//, "")
|
|
2149
|
+
.replace(/\.(tsx?)$/, "");
|
|
2150
|
+
return WORKFLOW_FOLLOW_UPS[id] ?? [];
|
|
2151
|
+
}
|