@smithers-orchestrator/cli 0.17.0 → 0.18.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.
@@ -1,4 +1,5 @@
1
1
  import { spawnSync } from "node:child_process";
2
+ import { createRequire } from "node:module";
2
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
4
  import { dirname, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
@@ -19,6 +20,9 @@ import { generateAgentsTs } from "./agent-detection.js";
19
20
  * @typedef {{ path: string; contents: string; preserveExisting?: boolean; }} TemplateFile
20
21
  */
21
22
 
23
+ const FALLBACK_SMITHERS_SPEC = "latest";
24
+ const require = createRequire(import.meta.url);
25
+
22
26
  /**
23
27
  * @param {string} path
24
28
  */
@@ -50,26 +54,33 @@ function readPackageVersion(path, fallback) {
50
54
  }
51
55
  }
52
56
  /**
53
- * Resolve `<spec>/package.json` via Node's module resolution (from this file's
54
- * location) and return its `version`. Uses `import.meta.resolve` so it works
55
- * whether the CLI is running from the monorepo checkout (deps under
56
- * `apps/cli/node_modules/`) or installed flat under a user's project
57
- * `node_modules/`. Falls back to the pin bundled at release time when the
58
- * package isn't installed in the user's project (typical for devDep-only
59
- * specs like `typescript` and `@types/*`).
57
+ * Resolve an installed dependency version from the current package layout.
60
58
  *
61
- * @param {string} spec
59
+ * @param {string} specifier
62
60
  * @param {string} fallback
63
61
  */
64
- function resolveDependencyVersion(spec, fallback) {
62
+ function resolveInstalledPackageVersion(specifier, fallback) {
65
63
  try {
66
- const url = import.meta.resolve(`${spec}/package.json`);
67
- return readPackageVersion(fileURLToPath(url), fallback);
64
+ const resolved = require.resolve(`${specifier}/package.json`);
65
+ return readPackageVersion(resolved, fallback);
68
66
  }
69
67
  catch {
70
68
  return fallback;
71
69
  }
72
70
  }
71
+ /**
72
+ * @returns {string | undefined}
73
+ */
74
+ function readOwnPackageVersion() {
75
+ try {
76
+ const ownPackagePath = fileURLToPath(new URL("../package.json", import.meta.url));
77
+ const version = readJson(ownPackagePath).version;
78
+ return typeof version === "string" && version.length > 0 ? version : undefined;
79
+ }
80
+ catch {
81
+ return undefined;
82
+ }
83
+ }
73
84
  /**
74
85
  * Pins shipped with this release for devDep-only specs that won't be in the
75
86
  * user's `node_modules` after `bunx smithers-orchestrator@latest init`. Bump
@@ -88,19 +99,22 @@ const BUNDLED_VERSION_PINS = {
88
99
  */
89
100
  function readDependencyVersions() {
90
101
  return {
91
- smithersVersion: readPackageVersion(fileURLToPath(new URL("../package.json", import.meta.url)), "0.0.0"),
92
- zodVersion: resolveDependencyVersion("zod", BUNDLED_VERSION_PINS.zod),
93
- typescriptVersion: resolveDependencyVersion("typescript", BUNDLED_VERSION_PINS.typescript),
94
- reactTypesVersion: resolveDependencyVersion("@types/react", BUNDLED_VERSION_PINS.reactTypes),
95
- reactDomTypesVersion: resolveDependencyVersion("@types/react-dom", BUNDLED_VERSION_PINS.reactDomTypes),
96
- mdxTypesVersion: resolveDependencyVersion("@types/mdx", BUNDLED_VERSION_PINS.mdxTypes),
97
- nodeTypesVersion: resolveDependencyVersion("@types/node", BUNDLED_VERSION_PINS.nodeTypes),
102
+ smithersVersion: readOwnPackageVersion(),
103
+ zodVersion: resolveInstalledPackageVersion("zod", BUNDLED_VERSION_PINS.zod),
104
+ typescriptVersion: resolveInstalledPackageVersion("typescript", BUNDLED_VERSION_PINS.typescript),
105
+ reactTypesVersion: resolveInstalledPackageVersion("@types/react", BUNDLED_VERSION_PINS.reactTypes),
106
+ reactDomTypesVersion: resolveInstalledPackageVersion("@types/react-dom", BUNDLED_VERSION_PINS.reactDomTypes),
107
+ mdxTypesVersion: resolveInstalledPackageVersion("@types/mdx", BUNDLED_VERSION_PINS.mdxTypes),
108
+ nodeTypesVersion: resolveInstalledPackageVersion("@types/node", BUNDLED_VERSION_PINS.nodeTypes),
98
109
  };
99
110
  }
100
111
  /**
101
112
  * @param {DependencyVersions} versions
102
113
  */
103
114
  function renderPackageJson(versions) {
115
+ const smithersSpec = versions.smithersVersion
116
+ ? `^${versions.smithersVersion}`
117
+ : FALLBACK_SMITHERS_SPEC;
104
118
  return JSON.stringify({
105
119
  name: "smithers-workflows",
106
120
  private: true,
@@ -113,7 +127,7 @@ function renderPackageJson(versions) {
113
127
  },
114
128
  dependencies: {
115
129
  skills: "github:mattpocock/skills",
116
- "smithers-orchestrator": "latest",
130
+ "smithers-orchestrator": smithersSpec,
117
131
  zod: versions.zodVersion,
118
132
  },
119
133
  devDependencies: {
@@ -590,6 +604,183 @@ function renderPrompts() {
590
604
  "",
591
605
  ].join("\n"),
592
606
  },
607
+ {
608
+ path: ".smithers/prompts/mission-plan.mdx",
609
+ contents: [
610
+ "# Mission Plan",
611
+ "",
612
+ "You are the mission orchestrator for a long-running Smithers workflow.",
613
+ "Scope the goal with the user before execution. If critical requirements, constraints, or acceptance criteria are missing, ask one question at a time using the ask-user command from the instructions you were given.",
614
+ "",
615
+ "Design the mission as serial milestones with targeted parallelism inside each milestone.",
616
+ "Each milestone must be a meaningful checkpoint that can be validated before the next milestone begins.",
617
+ "Each feature should be narrow enough for a fresh worker session to execute without needing the full mission history.",
618
+ "Include explicit validation checks for every milestone: tests, lint/typecheck/build commands, integration checks, and UI/browser walkthroughs when the repo has an app surface.",
619
+ "Capture risks, assumptions, out-of-scope items, and anything the user should approve before work starts.",
620
+ "",
621
+ "REQUEST:",
622
+ "{props.prompt}",
623
+ "",
624
+ "LIMITS:",
625
+ "- Max milestones: {props.maxMilestones}",
626
+ "- Max features per milestone: {props.maxFeaturesPerMilestone}",
627
+ "",
628
+ "REQUIRED OUTPUT:",
629
+ "{props.schema}",
630
+ "",
631
+ ].join("\n"),
632
+ },
633
+ {
634
+ path: ".smithers/prompts/mission-worker.mdx",
635
+ contents: [
636
+ "# Mission Worker",
637
+ "",
638
+ "You are a focused feature worker in a larger mission. Treat this as a fresh context window: use the mission plan below, execute only your assigned feature, and keep handoff notes precise.",
639
+ "",
640
+ "Rules:",
641
+ "1. Stay within the assigned feature scope unless you must make a small adjacent change to keep the repo working.",
642
+ "2. Prefer existing repo patterns and run the most relevant checks you can.",
643
+ "3. Record files changed, commands run, unresolved issues, and reusable learnings for later workers.",
644
+ "4. If the feature cannot be completed, make the best safe partial progress and explain exactly what blocks it.",
645
+ "",
646
+ "MISSION GOAL:",
647
+ "{props.missionGoal}",
648
+ "",
649
+ "MILESTONE:",
650
+ "```json",
651
+ "{JSON.stringify(props.milestone, null, 2)}",
652
+ "```",
653
+ "",
654
+ "FEATURE:",
655
+ "```json",
656
+ "{JSON.stringify(props.feature, null, 2)}",
657
+ "```",
658
+ "",
659
+ "{props.previousSummary ? `PREVIOUS MILESTONE SUMMARY:\\n${props.previousSummary}` : \"\"}",
660
+ "",
661
+ "REQUIRED OUTPUT:",
662
+ "{props.schema}",
663
+ "",
664
+ ].join("\n"),
665
+ },
666
+ {
667
+ path: ".smithers/prompts/mission-integrate.mdx",
668
+ contents: [
669
+ "# Mission Integrate",
670
+ "",
671
+ "You are integrating feature-worker results for one milestone.",
672
+ "",
673
+ "{props.useWorktrees ? \"The feature work may live on per-feature worktree branches. Inspect the branches, merge successful work back into the main workspace, resolve conflicts carefully, and leave conflicted or unsafe branches unmerged with a clear explanation.\" : \"Feature workers ran in the main workspace. Inspect their results and make any small integration fixes needed before validation.\"}",
674
+ "",
675
+ "MISSION GOAL:",
676
+ "{props.missionGoal}",
677
+ "",
678
+ "MILESTONE:",
679
+ "```json",
680
+ "{JSON.stringify(props.milestone, null, 2)}",
681
+ "```",
682
+ "",
683
+ "FEATURE RESULTS:",
684
+ "```json",
685
+ "{JSON.stringify(props.results, null, 2)}",
686
+ "```",
687
+ "",
688
+ "REQUIRED OUTPUT:",
689
+ "{props.schema}",
690
+ "",
691
+ ].join("\n"),
692
+ },
693
+ {
694
+ path: ".smithers/prompts/mission-validate.mdx",
695
+ contents: [
696
+ "# Mission Validate",
697
+ "",
698
+ "You are a validation worker for a mission milestone.",
699
+ "Validate accumulated work before the orchestrator moves to the next milestone.",
700
+ "",
701
+ "Run the strongest checks that fit the repo: tests, lint, typecheck, build, smoke tests, and integration checks.",
702
+ "If the repo has a UI, launch it and exercise core flows like a user would. Check render correctness, navigation, state transitions, and obvious layout regressions.",
703
+ "If a check cannot run because the repo lacks commands or setup, report that as a validation limitation instead of inventing results.",
704
+ "",
705
+ "MISSION GOAL:",
706
+ "{props.missionGoal}",
707
+ "",
708
+ "MILESTONE:",
709
+ "```json",
710
+ "{JSON.stringify(props.milestone, null, 2)}",
711
+ "```",
712
+ "",
713
+ "INTEGRATION RESULT:",
714
+ "```json",
715
+ "{JSON.stringify(props.integration, null, 2)}",
716
+ "```",
717
+ "",
718
+ "{props.followUp ? `FOLLOW-UP RESULT:\\n${JSON.stringify(props.followUp, null, 2)}` : \"\"}",
719
+ "",
720
+ "REQUIRED OUTPUT:",
721
+ "{props.schema}",
722
+ "",
723
+ ].join("\n"),
724
+ },
725
+ {
726
+ path: ".smithers/prompts/mission-follow-up.mdx",
727
+ contents: [
728
+ "# Mission Follow-Up",
729
+ "",
730
+ "Validation found issues in the current milestone. Fix the concrete regressions and gaps before the mission proceeds.",
731
+ "Keep changes targeted. Do not begin the next milestone.",
732
+ "",
733
+ "MISSION GOAL:",
734
+ "{props.missionGoal}",
735
+ "",
736
+ "MILESTONE:",
737
+ "```json",
738
+ "{JSON.stringify(props.milestone, null, 2)}",
739
+ "```",
740
+ "",
741
+ "VALIDATION RESULT:",
742
+ "```json",
743
+ "{JSON.stringify(props.validation, null, 2)}",
744
+ "```",
745
+ "",
746
+ "REQUIRED OUTPUT:",
747
+ "{props.schema}",
748
+ "",
749
+ ].join("\n"),
750
+ },
751
+ {
752
+ path: ".smithers/prompts/mission-final.mdx",
753
+ contents: [
754
+ "# Mission Final Report",
755
+ "",
756
+ "Write the final mission report. Summarize what shipped, what was validated, what remains risky, and the recommended next actions.",
757
+ "Be concrete about files, commands, validation gaps, and any milestone that did not pass validation.",
758
+ "",
759
+ "MISSION PLAN:",
760
+ "```json",
761
+ "{JSON.stringify(props.plan, null, 2)}",
762
+ "```",
763
+ "",
764
+ "FEATURE RESULTS:",
765
+ "```json",
766
+ "{JSON.stringify(props.featureResults, null, 2)}",
767
+ "```",
768
+ "",
769
+ "INTEGRATION RESULTS:",
770
+ "```json",
771
+ "{JSON.stringify(props.integrationResults, null, 2)}",
772
+ "```",
773
+ "",
774
+ "VALIDATION RESULTS:",
775
+ "```json",
776
+ "{JSON.stringify(props.validationResults, null, 2)}",
777
+ "```",
778
+ "",
779
+ "REQUIRED OUTPUT:",
780
+ "{props.schema}",
781
+ "",
782
+ ].join("\n"),
783
+ },
593
784
  {
594
785
  path: ".smithers/prompts/tickets-create.mdx",
595
786
  contents: [
@@ -1974,6 +2165,440 @@ function renderWorkflows() {
1974
2165
  " </Workflow>",
1975
2166
  "));",
1976
2167
  ]),
2168
+ renderWorkflowFile("mission", "Mission", [
2169
+ ...sharedImports,
2170
+ 'import AskUserInstructions from "../prompts/ask-user-instructions.mdx";',
2171
+ 'import MissionPlanPrompt from "../prompts/mission-plan.mdx";',
2172
+ 'import MissionWorkerPrompt from "../prompts/mission-worker.mdx";',
2173
+ 'import MissionIntegratePrompt from "../prompts/mission-integrate.mdx";',
2174
+ 'import MissionValidatePrompt from "../prompts/mission-validate.mdx";',
2175
+ 'import MissionFollowUpPrompt from "../prompts/mission-follow-up.mdx";',
2176
+ 'import MissionFinalPrompt from "../prompts/mission-final.mdx";',
2177
+ "",
2178
+ "const missionFeatureSchema = z.looseObject({",
2179
+ ' id: z.string().default("feature"),',
2180
+ ' title: z.string().default("Feature"),',
2181
+ ' instructions: z.string().default("Complete the assigned feature."),',
2182
+ " files: z.array(z.string()).default([]),",
2183
+ " validation: z.array(z.string()).default([]),",
2184
+ ' workerType: z.enum(["implementation", "test", "docs", "research"]).default("implementation"),',
2185
+ " canRunInParallel: z.boolean().default(true),",
2186
+ "});",
2187
+ "",
2188
+ "const missionMilestoneSchema = z.looseObject({",
2189
+ ' id: z.string().default("milestone"),',
2190
+ ' title: z.string().default("Milestone"),',
2191
+ ' objective: z.string().default("Complete this milestone."),',
2192
+ " features: z.array(missionFeatureSchema).default([]),",
2193
+ " validationPlan: z.array(z.string()).default([]),",
2194
+ "});",
2195
+ "",
2196
+ "const missionPlanSchema = z.looseObject({",
2197
+ ' goal: z.string().default(""),',
2198
+ ' summary: z.string().default("Mission plan created."),',
2199
+ " milestones: z.array(missionMilestoneSchema).default([]),",
2200
+ " assumptions: z.array(z.string()).default([]),",
2201
+ " risks: z.array(z.string()).default([]),",
2202
+ " outOfScope: z.array(z.string()).default([]),",
2203
+ ' approvalNotes: z.string().nullable().default(null),',
2204
+ "});",
2205
+ "",
2206
+ "const missionApprovalSchema = z.looseObject({",
2207
+ " approved: z.boolean().default(false),",
2208
+ " note: z.string().nullable().default(null),",
2209
+ " decidedBy: z.string().nullable().default(null),",
2210
+ " decidedAt: z.string().nullable().default(null),",
2211
+ "});",
2212
+ "",
2213
+ "const missionFeatureResultSchema = z.looseObject({",
2214
+ ' featureId: z.string().default("feature"),',
2215
+ ' status: z.enum(["success", "partial", "failed"]).default("partial"),',
2216
+ ' summary: z.string().default("Feature worker completed."),',
2217
+ " filesChanged: z.array(z.string()).default([]),",
2218
+ " commandsRun: z.array(z.string()).default([]),",
2219
+ " blockers: z.array(z.string()).default([]),",
2220
+ " reusableLearnings: z.array(z.string()).default([]),",
2221
+ "});",
2222
+ "",
2223
+ "const milestoneIntegrationSchema = z.looseObject({",
2224
+ ' milestoneId: z.string().default("milestone"),',
2225
+ ' status: z.enum(["integrated", "partial", "blocked"]).default("integrated"),',
2226
+ ' summary: z.string().default("Milestone integrated."),',
2227
+ " mergedBranches: z.array(z.string()).default([]),",
2228
+ " conflictedBranches: z.array(z.string()).default([]),",
2229
+ " filesChanged: z.array(z.string()).default([]),",
2230
+ "});",
2231
+ "",
2232
+ "const milestoneValidationSchema = z.looseObject({",
2233
+ ' milestoneId: z.string().default("milestone"),',
2234
+ " passed: z.boolean().default(true),",
2235
+ ' summary: z.string().default("Validation completed."),',
2236
+ " checks: z.array(z.object({",
2237
+ " name: z.string(),",
2238
+ ' status: z.enum(["passed", "failed", "skipped"]),',
2239
+ " details: z.string().nullable().default(null),",
2240
+ " })).default([]),",
2241
+ " regressions: z.array(z.string()).default([]),",
2242
+ " followUps: z.array(z.string()).default([]),",
2243
+ "});",
2244
+ "",
2245
+ "const missionFinalSchema = z.looseObject({",
2246
+ ' status: z.enum(["completed", "partial", "cancelled"]).default("completed"),',
2247
+ ' summary: z.string().default("Mission complete."),',
2248
+ " completedMilestones: z.number().int().default(0),",
2249
+ " totalMilestones: z.number().int().default(0),",
2250
+ " validationPassed: z.boolean().default(true),",
2251
+ " remainingRisks: z.array(z.string()).default([]),",
2252
+ " nextActions: z.array(z.string()).default([]),",
2253
+ " markdownBody: z.string().default(\"\"),",
2254
+ "});",
2255
+ "",
2256
+ "const inputSchema = z.object({",
2257
+ ' prompt: z.string().default("Describe the mission goal."),',
2258
+ " requirePlanApproval: z.boolean().default(true),",
2259
+ " maxMilestones: z.number().int().min(1).max(20).default(6),",
2260
+ " maxFeaturesPerMilestone: z.number().int().min(1).max(20).default(6),",
2261
+ " maxConcurrency: z.number().int().min(1).max(10).default(3),",
2262
+ " useWorktrees: z.boolean().default(false),",
2263
+ ' baseBranch: z.string().default("main"),',
2264
+ "});",
2265
+ "",
2266
+ "const { Workflow, Task, Sequence, Parallel, Approval, Worktree, smithers, outputs } = createSmithers({",
2267
+ " input: inputSchema,",
2268
+ " missionPlan: missionPlanSchema,",
2269
+ " missionApproval: missionApprovalSchema,",
2270
+ " missionFeature: missionFeatureResultSchema,",
2271
+ " milestoneIntegration: milestoneIntegrationSchema,",
2272
+ " milestoneValidation: milestoneValidationSchema,",
2273
+ " missionFinal: missionFinalSchema,",
2274
+ "});",
2275
+ "",
2276
+ 'const missionMemory = { kind: "workflow", id: "mission" } as const;',
2277
+ "",
2278
+ "function slugify(value: unknown, fallback: string): string {",
2279
+ ' const normalized = String(value ?? "")',
2280
+ " .toLowerCase()",
2281
+ ' .replace(/[^a-z0-9]+/g, "-")',
2282
+ ' .replace(/^-+|-+$/g, "");',
2283
+ " return normalized.length > 0 ? normalized : fallback;",
2284
+ "}",
2285
+ "",
2286
+ "function asStringArray(value: unknown): string[] {",
2287
+ " return Array.isArray(value) ? value.map((entry) => String(entry)).filter(Boolean) : [];",
2288
+ "}",
2289
+ "",
2290
+ "function normalizeFeature(feature: any, index: number): any {",
2291
+ " const title = typeof feature?.title === \"string\" && feature.title.length > 0",
2292
+ " ? feature.title",
2293
+ " : `Feature ${index + 1}`;",
2294
+ " return {",
2295
+ " id: slugify(feature?.id ?? title, `feature-${index + 1}`),",
2296
+ " title,",
2297
+ " instructions: typeof feature?.instructions === \"string\" && feature.instructions.length > 0",
2298
+ " ? feature.instructions",
2299
+ " : `Complete ${title}.`,",
2300
+ " files: asStringArray(feature?.files),",
2301
+ " validation: asStringArray(feature?.validation),",
2302
+ " workerType: [\"implementation\", \"test\", \"docs\", \"research\"].includes(feature?.workerType)",
2303
+ " ? feature.workerType",
2304
+ ' : "implementation",',
2305
+ " canRunInParallel: feature?.canRunInParallel !== false,",
2306
+ " };",
2307
+ "}",
2308
+ "",
2309
+ "function normalizeMilestones(plan: any, maxMilestones: number, maxFeaturesPerMilestone: number): any[] {",
2310
+ " return (Array.isArray(plan?.milestones) ? plan.milestones : [])",
2311
+ " .slice(0, maxMilestones)",
2312
+ " .map((milestone: any, index: number) => {",
2313
+ " const title = typeof milestone?.title === \"string\" && milestone.title.length > 0",
2314
+ " ? milestone.title",
2315
+ " : `Milestone ${index + 1}`;",
2316
+ " const features = (Array.isArray(milestone?.features) ? milestone.features : [])",
2317
+ " .slice(0, maxFeaturesPerMilestone)",
2318
+ " .map((feature: any, featureIndex: number) => normalizeFeature(feature, featureIndex));",
2319
+ " return {",
2320
+ " id: slugify(milestone?.id ?? title, `milestone-${index + 1}`),",
2321
+ " title,",
2322
+ " objective: typeof milestone?.objective === \"string\" && milestone.objective.length > 0",
2323
+ " ? milestone.objective",
2324
+ " : title,",
2325
+ " validationPlan: asStringArray(milestone?.validationPlan),",
2326
+ " features: features.length > 0",
2327
+ " ? features",
2328
+ " : [normalizeFeature({ title, instructions: milestone?.objective ?? title }, 0)],",
2329
+ " };",
2330
+ " });",
2331
+ "}",
2332
+ "",
2333
+ "function featureTaskId(milestoneIndex: number, feature: any): string {",
2334
+ " return `mission:milestone:${milestoneIndex + 1}:feature:${feature.id}`;",
2335
+ "}",
2336
+ "",
2337
+ "function milestoneIntegrateId(milestoneIndex: number): string {",
2338
+ " return `mission:milestone:${milestoneIndex + 1}:integrate`;",
2339
+ "}",
2340
+ "",
2341
+ "function milestoneValidationId(milestoneIndex: number): string {",
2342
+ " return `mission:milestone:${milestoneIndex + 1}:validate`;",
2343
+ "}",
2344
+ "",
2345
+ "function milestoneFollowUpId(milestoneIndex: number): string {",
2346
+ " return `mission:milestone:${milestoneIndex + 1}:follow-up`;",
2347
+ "}",
2348
+ "",
2349
+ "function milestoneRevalidationId(milestoneIndex: number): string {",
2350
+ " return `mission:milestone:${milestoneIndex + 1}:revalidate`;",
2351
+ "}",
2352
+ "",
2353
+ "function featureNeeds(milestoneIndex: number, features: any[]): Record<string, string> {",
2354
+ " return Object.fromEntries(features.map((feature, index) => [`feature${index}`, featureTaskId(milestoneIndex, feature)]));",
2355
+ "}",
2356
+ "",
2357
+ "function featureDeps(features: any[]): Record<string, typeof missionFeatureResultSchema> {",
2358
+ " return Object.fromEntries(features.map((_, index) => [`feature${index}`, outputs.missionFeature]));",
2359
+ "}",
2360
+ "",
2361
+ "function workerAgentsFor(feature: any): any {",
2362
+ " if (feature.workerType === \"research\") return agents.smartTool;",
2363
+ " if (feature.workerType === \"docs\") return agents.cheapFast;",
2364
+ " return agents.smart;",
2365
+ "}",
2366
+ "",
2367
+ "function previousMilestoneSummary(ctx: any): string {",
2368
+ " const integrations = ctx.outputs.milestoneIntegration ?? [];",
2369
+ " const validations = ctx.outputs.milestoneValidation ?? [];",
2370
+ " return [",
2371
+ " ...integrations.map((entry: any) => `Integration: ${entry.summary}`),",
2372
+ " ...validations.map((entry: any) => `Validation: ${entry.passed ? \"passed\" : \"failed\"} - ${entry.summary}`),",
2373
+ " ].slice(-8).join(\"\\n\");",
2374
+ "}",
2375
+ "",
2376
+ "function milestoneIsTerminal(ctx: any, milestoneIndex: number): boolean {",
2377
+ " const revalidation = ctx.outputMaybe(\"milestoneValidation\", { nodeId: milestoneRevalidationId(milestoneIndex) });",
2378
+ " if (revalidation) return true;",
2379
+ " const validation = ctx.outputMaybe(\"milestoneValidation\", { nodeId: milestoneValidationId(milestoneIndex) });",
2380
+ " return Boolean(validation && validation.passed !== false);",
2381
+ "}",
2382
+ "",
2383
+ "function activeMilestoneIndex(ctx: any, milestones: any[]): number {",
2384
+ " for (let index = 0; index < milestones.length; index += 1) {",
2385
+ " if (!milestoneIsTerminal(ctx, index)) return index;",
2386
+ " }",
2387
+ " return milestones.length;",
2388
+ "}",
2389
+ "",
2390
+ "function renderFeatureWorker(ctx: any, plan: any, milestone: any, milestoneIndex: number, feature: any) {",
2391
+ " const taskId = featureTaskId(milestoneIndex, feature);",
2392
+ " const workerTask = (",
2393
+ " <Task",
2394
+ " key={taskId}",
2395
+ " id={taskId}",
2396
+ " output={outputs.missionFeature}",
2397
+ " agent={workerAgentsFor(feature)}",
2398
+ " timeoutMs={3_600_000}",
2399
+ " heartbeatTimeoutMs={900_000}",
2400
+ " continueOnFail",
2401
+ " memory={{",
2402
+ " recall: { namespace: missionMemory, query: `${plan.goal} ${milestone.title} ${feature.title}`, topK: 5 },",
2403
+ " remember: { namespace: missionMemory, key: taskId },",
2404
+ " }}",
2405
+ " >",
2406
+ " <MissionWorkerPrompt",
2407
+ " missionGoal={plan.goal || ctx.input.prompt}",
2408
+ " milestone={milestone}",
2409
+ " feature={feature}",
2410
+ " previousSummary={previousMilestoneSummary(ctx)}",
2411
+ " />",
2412
+ " </Task>",
2413
+ " );",
2414
+ "",
2415
+ " if (!(ctx.input.useWorktrees ?? false)) return workerTask;",
2416
+ "",
2417
+ " return (",
2418
+ " <Worktree",
2419
+ " key={taskId}",
2420
+ " id={`mission-worktree-${milestoneIndex + 1}-${feature.id}`}",
2421
+ " path={`.worktrees/mission-${milestoneIndex + 1}-${feature.id}`}",
2422
+ " branch={`mission/${milestoneIndex + 1}/${feature.id}`}",
2423
+ " baseBranch={ctx.input.baseBranch ?? \"main\"}",
2424
+ " >",
2425
+ " {workerTask}",
2426
+ " </Worktree>",
2427
+ " );",
2428
+ "}",
2429
+ "",
2430
+ "function renderMilestone(ctx: any, plan: any, milestone: any, milestoneIndex: number) {",
2431
+ " const features = milestone.features;",
2432
+ " const integrationId = milestoneIntegrateId(milestoneIndex);",
2433
+ " const validationId = milestoneValidationId(milestoneIndex);",
2434
+ " const integration = ctx.outputMaybe(\"milestoneIntegration\", { nodeId: integrationId });",
2435
+ " const validation = ctx.outputMaybe(\"milestoneValidation\", { nodeId: validationId });",
2436
+ " const needsFollowUp = Boolean(validation && validation.passed === false);",
2437
+ "",
2438
+ " return (",
2439
+ " <Sequence>",
2440
+ " <Parallel maxConcurrency={Math.min(ctx.input.maxConcurrency ?? 3, features.length)}>",
2441
+ " {features.map((feature: any) => renderFeatureWorker(ctx, plan, milestone, milestoneIndex, feature))}",
2442
+ " </Parallel>",
2443
+ " <Task",
2444
+ " id={integrationId}",
2445
+ " output={outputs.milestoneIntegration}",
2446
+ " agent={agents.smartTool}",
2447
+ " needs={featureNeeds(milestoneIndex, features)}",
2448
+ " deps={featureDeps(features)}",
2449
+ " timeoutMs={1_800_000}",
2450
+ " memory={{ remember: { namespace: missionMemory, key: integrationId } }}",
2451
+ " >",
2452
+ " {(deps: any) => {",
2453
+ " const results = features.map((_: any, index: number) => deps[`feature${index}`]);",
2454
+ " return (",
2455
+ " <MissionIntegratePrompt",
2456
+ " missionGoal={plan.goal || ctx.input.prompt}",
2457
+ " milestone={milestone}",
2458
+ " results={results}",
2459
+ " useWorktrees={ctx.input.useWorktrees ?? false}",
2460
+ " />",
2461
+ " );",
2462
+ " }}",
2463
+ " </Task>",
2464
+ " <Task",
2465
+ " id={validationId}",
2466
+ " output={outputs.milestoneValidation}",
2467
+ " agent={agents.smart}",
2468
+ " needs={{ integration: integrationId }}",
2469
+ " deps={{ integration: outputs.milestoneIntegration }}",
2470
+ " timeoutMs={1_800_000}",
2471
+ " heartbeatTimeoutMs={900_000}",
2472
+ " memory={{ remember: { namespace: missionMemory, key: validationId } }}",
2473
+ " >",
2474
+ " {(deps: any) => (",
2475
+ " <MissionValidatePrompt",
2476
+ " missionGoal={plan.goal || ctx.input.prompt}",
2477
+ " milestone={milestone}",
2478
+ " integration={deps.integration}",
2479
+ " />",
2480
+ " )}",
2481
+ " </Task>",
2482
+ " {needsFollowUp && (",
2483
+ " <Sequence>",
2484
+ " <Task",
2485
+ " id={milestoneFollowUpId(milestoneIndex)}",
2486
+ " output={outputs.missionFeature}",
2487
+ " agent={agents.smart}",
2488
+ " needs={{ validation: validationId }}",
2489
+ " deps={{ validation: outputs.milestoneValidation }}",
2490
+ " timeoutMs={1_800_000}",
2491
+ " memory={{ remember: { namespace: missionMemory, key: milestoneFollowUpId(milestoneIndex) } }}",
2492
+ " >",
2493
+ " {(deps: any) => (",
2494
+ " <MissionFollowUpPrompt",
2495
+ " missionGoal={plan.goal || ctx.input.prompt}",
2496
+ " milestone={milestone}",
2497
+ " validation={deps.validation}",
2498
+ " />",
2499
+ " )}",
2500
+ " </Task>",
2501
+ " <Task",
2502
+ " id={milestoneRevalidationId(milestoneIndex)}",
2503
+ " output={outputs.milestoneValidation}",
2504
+ " agent={agents.smart}",
2505
+ " needs={{ followUp: milestoneFollowUpId(milestoneIndex) }}",
2506
+ " deps={{ followUp: outputs.missionFeature }}",
2507
+ " timeoutMs={1_800_000}",
2508
+ " heartbeatTimeoutMs={900_000}",
2509
+ " memory={{ remember: { namespace: missionMemory, key: milestoneRevalidationId(milestoneIndex) } }}",
2510
+ " >",
2511
+ " {(deps: any) => (",
2512
+ " <MissionValidatePrompt",
2513
+ " missionGoal={plan.goal || ctx.input.prompt}",
2514
+ " milestone={milestone}",
2515
+ " integration={integration}",
2516
+ " followUp={deps.followUp}",
2517
+ " />",
2518
+ " )}",
2519
+ " </Task>",
2520
+ " </Sequence>",
2521
+ " )}",
2522
+ " </Sequence>",
2523
+ " );",
2524
+ "}",
2525
+ "",
2526
+ "function renderFinal(ctx: any, plan: any, milestones: any[]) {",
2527
+ " return (",
2528
+ " <Task id=\"mission:final\" output={outputs.missionFinal} agent={agents.smartTool}>",
2529
+ " <MissionFinalPrompt",
2530
+ " plan={{ ...plan, milestones }}",
2531
+ " featureResults={ctx.outputs.missionFeature ?? []}",
2532
+ " integrationResults={ctx.outputs.milestoneIntegration ?? []}",
2533
+ " validationResults={ctx.outputs.milestoneValidation ?? []}",
2534
+ " />",
2535
+ " </Task>",
2536
+ " );",
2537
+ "}",
2538
+ "",
2539
+ "export default smithers((ctx) => {",
2540
+ " const plan = ctx.outputMaybe(\"missionPlan\", { nodeId: \"mission:plan\" });",
2541
+ " const approval = ctx.outputMaybe(\"missionApproval\", { nodeId: \"mission:approve-plan\" });",
2542
+ " const approvalRequired = ctx.input.requirePlanApproval;",
2543
+ " const approvalDenied = approvalRequired && approval && approval.approved === false;",
2544
+ " const approved = !approvalRequired || approval?.approved === true;",
2545
+ " const milestones = normalizeMilestones(plan, ctx.input.maxMilestones ?? 6, ctx.input.maxFeaturesPerMilestone ?? 6);",
2546
+ " const activeIndex = approved ? activeMilestoneIndex(ctx, milestones) : 0;",
2547
+ "",
2548
+ " return (",
2549
+ " <Workflow name=\"mission\">",
2550
+ " <Sequence>",
2551
+ " <Task",
2552
+ " id=\"mission:plan\"",
2553
+ " output={outputs.missionPlan}",
2554
+ " agent={agents.smartTool}",
2555
+ " timeoutMs={1_800_000}",
2556
+ " heartbeatTimeoutMs={900_000}",
2557
+ " memory={{ remember: { namespace: missionMemory, key: \"mission:plan\" } }}",
2558
+ " >",
2559
+ " <AskUserInstructions />",
2560
+ " <MissionPlanPrompt",
2561
+ " prompt={ctx.input.prompt}",
2562
+ " maxMilestones={ctx.input.maxMilestones ?? 6}",
2563
+ " maxFeaturesPerMilestone={ctx.input.maxFeaturesPerMilestone ?? 6}",
2564
+ " />",
2565
+ " </Task>",
2566
+ "",
2567
+ " {plan && approvalRequired && !approval && (",
2568
+ " <Approval",
2569
+ " id=\"mission:approve-plan\"",
2570
+ " output={outputs.missionApproval}",
2571
+ " request={{",
2572
+ " title: \"Approve mission plan?\",",
2573
+ " summary: plan.summary || \"Review the scoped mission plan before workers begin.\",",
2574
+ " metadata: { milestones: milestones.length, risks: plan.risks ?? [] },",
2575
+ " }}",
2576
+ " onDeny=\"continue\"",
2577
+ " />",
2578
+ " )}",
2579
+ "",
2580
+ " {approvalDenied && (",
2581
+ " <Task id=\"mission:cancelled\" output={outputs.missionFinal}>",
2582
+ " {{",
2583
+ " status: \"cancelled\",",
2584
+ " summary: `Mission plan was not approved. ${approval?.note ?? \"\"}`.trim(),",
2585
+ " completedMilestones: 0,",
2586
+ " totalMilestones: milestones.length,",
2587
+ " validationPassed: false,",
2588
+ " remainingRisks: plan?.risks ?? [],",
2589
+ " nextActions: [\"Revise the mission scope and rerun the workflow.\"],",
2590
+ " markdownBody: \"# Mission Cancelled\\n\\nThe plan was not approved.\",",
2591
+ " }}",
2592
+ " </Task>",
2593
+ " )}",
2594
+ "",
2595
+ " {plan && approved && activeIndex < milestones.length && renderMilestone(ctx, plan, milestones[activeIndex], activeIndex)}",
2596
+ " {plan && approved && activeIndex >= milestones.length && renderFinal(ctx, plan, milestones)}",
2597
+ " </Sequence>",
2598
+ " </Workflow>",
2599
+ " );",
2600
+ "});",
2601
+ ]),
1977
2602
  {
1978
2603
  path: ".smithers/workflows/kanban.tsx",
1979
2604
  contents: [
@@ -2291,8 +2916,10 @@ const WORKFLOW_FOLLOW_UPS = {
2291
2916
  "research": [
2292
2917
  { command: "workflow run write-a-prd", description: "Formalize findings into a PRD" },
2293
2918
  { command: "workflow run plan", description: "Turn research into an implementation plan" },
2919
+ { command: "workflow run mission", description: "Run a scoped long-horizon mission" },
2294
2920
  ],
2295
2921
  "plan": [
2922
+ { command: "workflow run mission", description: "Execute as a milestone-gated mission" },
2296
2923
  { command: "workflow run research-plan-implement", description: "Research, plan, and execute" },
2297
2924
  { command: "workflow run implement", description: "Execute the plan" },
2298
2925
  { command: "workflow run tickets-create", description: "Break plan into tickets" },
@@ -2315,6 +2942,7 @@ const WORKFLOW_FOLLOW_UPS = {
2315
2942
  { command: "workflow run tickets-create", description: "Break directly into tickets" },
2316
2943
  ],
2317
2944
  "write-a-prd": [
2945
+ { command: "workflow run mission", description: "Execute the PRD as a mission" },
2318
2946
  { command: "workflow run tickets-create", description: "Break PRD into implementable tickets" },
2319
2947
  { command: "workflow run plan", description: "Turn PRD into a phased plan" },
2320
2948
  { command: "workflow run implement", description: "Start building from the PRD" },
@@ -2331,6 +2959,11 @@ const WORKFLOW_FOLLOW_UPS = {
2331
2959
  { command: "workflow run review", description: "Review the changes" },
2332
2960
  { command: "workflow run improve-test-coverage", description: "Improve test coverage" },
2333
2961
  ],
2962
+ "mission": [
2963
+ { command: "workflow run review", description: "Review mission changes" },
2964
+ { command: "workflow run improve-test-coverage", description: "Fill validation gaps" },
2965
+ { command: "workflow run audit", description: "Audit completed feature areas" },
2966
+ ],
2334
2967
  "review": [
2335
2968
  { command: "workflow run implement", description: "Address review feedback" },
2336
2969
  ],