moflo 4.9.20 → 4.9.21

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.
Files changed (82) hide show
  1. package/.claude/commands/{simplify.md → flo-simplify.md} +4 -4
  2. package/.claude/guidance/shipped/moflo-agent-rules.md +172 -0
  3. package/.claude/guidance/shipped/moflo-claude-swarm-cohesion.md +73 -265
  4. package/.claude/guidance/shipped/moflo-cli-reference.md +6 -6
  5. package/.claude/guidance/shipped/moflo-core-guidance.md +66 -184
  6. package/.claude/guidance/shipped/moflo-cross-platform.md +1 -1
  7. package/.claude/guidance/shipped/moflo-error-handling.md +3 -3
  8. package/.claude/guidance/shipped/moflo-guidance-rules.md +17 -7
  9. package/.claude/guidance/shipped/moflo-memory-strategy.md +76 -182
  10. package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +6 -8
  11. package/.claude/guidance/shipped/moflo-settings-injection.md +7 -9
  12. package/.claude/guidance/shipped/moflo-source-hygiene.md +5 -5
  13. package/.claude/guidance/shipped/moflo-spell-connectors.md +3 -4
  14. package/.claude/guidance/shipped/moflo-spell-custom-steps.md +3 -4
  15. package/.claude/guidance/shipped/moflo-spell-engine.md +40 -162
  16. package/.claude/guidance/shipped/moflo-spell-runner.md +134 -0
  17. package/.claude/guidance/shipped/moflo-spell-sandboxing.md +10 -57
  18. package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +149 -0
  19. package/.claude/guidance/shipped/moflo-subagents.md +43 -114
  20. package/.claude/guidance/shipped/moflo-task-icons.md +4 -4
  21. package/.claude/guidance/shipped/moflo-user-facing-language.md +3 -3
  22. package/.claude/guidance/shipped/moflo-verbose-command-filtering.md +3 -3
  23. package/.claude/guidance/shipped/moflo-yaml-reference.md +4 -5
  24. package/.claude/helpers/gate.cjs +124 -14
  25. package/.claude/helpers/prompt-hook.mjs +4 -38
  26. package/.claude/helpers/simplify-classify.cjs +32 -11
  27. package/.claude/helpers/subagent-bootstrap.json +1 -1
  28. package/.claude/helpers/subagent-start.cjs +1 -1
  29. package/.claude/skills/connector-builder/SKILL.md +42 -429
  30. package/.claude/skills/connector-builder/templates/connector.md +189 -0
  31. package/.claude/skills/connector-builder/templates/step-command.md +176 -0
  32. package/.claude/skills/eldar/SKILL.md +7 -7
  33. package/.claude/skills/fl/SKILL.md +3 -3
  34. package/.claude/skills/fl/execution-modes.md +3 -3
  35. package/.claude/skills/fl/phases.md +3 -3
  36. package/.claude/skills/{simplify → flo-simplify}/SKILL.md +11 -11
  37. package/.claude/skills/guidance/SKILL.md +17 -9
  38. package/.claude/skills/memory-patterns/SKILL.md +1 -1
  39. package/.claude/skills/publish/SKILL.md +121 -36
  40. package/.claude/skills/reset-epic/SKILL.md +2 -2
  41. package/.claude/skills/spell-builder/SKILL.md +39 -226
  42. package/.claude/skills/spell-builder/architecture.md +1 -1
  43. package/.claude/skills/spell-builder/permissions.md +107 -0
  44. package/.claude/skills/spell-builder/preflight.md +101 -0
  45. package/.claude/skills/spell-schedule/SKILL.md +2 -3
  46. package/bin/gate.cjs +124 -14
  47. package/bin/prompt-hook.mjs +4 -38
  48. package/bin/session-start-launcher.mjs +19 -1
  49. package/bin/setup-project.mjs +63 -69
  50. package/bin/simplify-classify.cjs +32 -11
  51. package/dist/src/cli/commands/doctor-checks-deep.js +4 -0
  52. package/dist/src/cli/init/claudemd-generator.js +30 -33
  53. package/dist/src/cli/init/executor.js +28 -16
  54. package/dist/src/cli/init/helpers-generator.js +101 -51
  55. package/dist/src/cli/init/moflo-init.js +41 -114
  56. package/dist/src/cli/init/settings-generator.js +32 -14
  57. package/dist/src/cli/services/hook-block-hash.js +7 -2
  58. package/dist/src/cli/services/hook-wiring.js +86 -3
  59. package/dist/src/cli/services/subagent-bootstrap.js +1 -1
  60. package/dist/src/cli/version.js +1 -1
  61. package/package.json +2 -2
  62. package/scripts/post-install-bootstrap.mjs +19 -0
  63. package/.claude/guidance/shipped/moflo-session-start.md +0 -154
  64. package/.claude/guidance/shipped/moflo-spell-engine-architecture.md +0 -145
  65. package/.claude/skills/browser/SKILL.md +0 -204
  66. package/.claude/skills/github-code-review/SKILL.md +0 -1140
  67. package/.claude/skills/github-multi-repo/SKILL.md +0 -866
  68. package/.claude/skills/github-project-management/SKILL.md +0 -1272
  69. package/.claude/skills/github-release-management/SKILL.md +0 -1074
  70. package/.claude/skills/github-workflow-automation/SKILL.md +0 -1060
  71. package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
  72. package/.claude/skills/hooks-automation/SKILL.md +0 -1193
  73. package/.claude/skills/pair-programming/SKILL.md +0 -1202
  74. package/.claude/skills/performance-analysis/SKILL.md +0 -563
  75. package/.claude/skills/skill-builder/SKILL.md +0 -910
  76. package/.claude/skills/sparc-methodology/SKILL.md +0 -904
  77. package/.claude/skills/stream-chain/SKILL.md +0 -563
  78. package/.claude/skills/swarm-advanced/SKILL.md +0 -811
  79. package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
  80. package/.claude/skills/verification-quality/SKILL.md +0 -649
  81. package/.claude/skills/worker-benchmarks/skill.md +0 -135
  82. package/.claude/skills/worker-integration/skill.md +0 -154
@@ -5,7 +5,17 @@ description: "Scaffold new spell step commands and connectors. Use when building
5
5
 
6
6
  # Connector Builder
7
7
 
8
- Scaffold production-ready step commands (`StepCommand`) and, when truly needed, generalized I/O connectors (`SpellConnector`) with proper types, tests, and registration.
8
+ Purpose: scaffold production-ready step commands (`StepCommand`) and, when truly needed, generalized I/O connectors (`SpellConnector`) with proper types, tests, and registration.
9
+
10
+ ## Read First — Companion Files
11
+
12
+ | File | When to read |
13
+ |------|--------------|
14
+ | [templates/connector.md](templates/connector.md) | When generating a generalized connector — full source + test + registration scaffold |
15
+ | [templates/step-command.md](templates/step-command.md) | When generating a step command — full source + test + registration scaffold |
16
+ | [../spell-builder/architecture.md](../spell-builder/architecture.md) | **Always** — three-layer model and connector-vs-step decision tree |
17
+ | [../spell-builder/permissions.md](../spell-builder/permissions.md) | When defining capabilities — required disclosure format |
18
+ | [../spell-builder/preflight.md](../spell-builder/preflight.md) | When authoring preflight checks — copywriting rules for user-visible `reason` strings |
9
19
 
10
20
  ## Prerequisites
11
21
 
@@ -30,11 +40,9 @@ Ask the user:
30
40
  > 1. **Step command** — executes logic within a spell step (transform data, control flow, etc.)
31
41
  > 2. **Generalized connector** — wraps a new I/O transport type (e.g., WebSocket, gRPC, MQTT)
32
42
 
33
- **Important:** Simple service integrations (Slack webhook, S3 upload, Jira comment) should compose existing connectors (`http`, `github-cli`, `playwright`) in spell YAML — no dedicated connector needed. However, platforms requiring complex multi-step browser interaction (like Outlook.com web UI) DO warrant a dedicated connector. See `.claude/skills/spell-builder/architecture.md` for the decision tree.
34
-
35
- **Documentation requirement:** When creating any new step command or connector, you MUST also create a README.md following `.claude/guidance/shipped/moflo-guidance-rules.md`. Use existing READMEs in `.claude/skills/spell-builder/steps/` or `connectors/` as templates. Apply automatically — the user should never need to ask.
43
+ **Important:** Simple service integrations (Slack webhook, S3 upload, Jira comment) should compose existing connectors (`http`, `github-cli`, `playwright`) in spell YAML — no dedicated connector needed. However, platforms requiring complex multi-step browser interaction (like Outlook.com web UI) DO warrant a dedicated connector. See [../spell-builder/architecture.md](../spell-builder/architecture.md) for the decision tree.
36
44
 
37
- Then follow the appropriate section below.
45
+ **Documentation requirement:** When creating any new step command or connector, you MUST also create a README.md following `.claude/guidance/moflo-guidance-rules.md`. Use existing READMEs in `.claude/skills/spell-builder/steps/` or `connectors/` as templates. Apply automatically — the user should never need to ask.
38
46
 
39
47
  ---
40
48
 
@@ -44,14 +52,12 @@ Then follow the appropriate section below.
44
52
 
45
53
  ### Step 1: Gather Requirements
46
54
 
47
- Ask the user for:
48
-
49
55
  | Field | Required | Example |
50
56
  |-------|----------|---------|
51
57
  | **Name** | Yes | `websocket`, `grpc`, `mqtt` |
52
58
  | **Description** | Yes | `WebSocket bidirectional messaging` |
53
59
  | **Version** | Yes (default `1.0.0`) | `1.0.0` |
54
- | **Capabilities** | Yes | `read`, `write`, `search`, `subscribe`, `authenticate` |
60
+ | **Capabilities** | Yes pick from `read`, `write`, `search`, `subscribe`, `authenticate` | `read`, `write` |
55
61
  | **Actions** | Yes (at least 1) | `connect`, `send`, `receive`, `close` |
56
62
 
57
63
  For each action, ask:
@@ -60,195 +66,33 @@ For each action, ask:
60
66
  - Input parameters (name, type, required?)
61
67
  - Output fields (name, type)
62
68
 
63
- **Verify the connector is generalized:** The name should describe an I/O transport, not a service. `websocket` is correct; `slack` is not.
64
-
65
- ### Step 2: Generate Connector Source
69
+ **Verify the connector is generalized:** the name should describe an I/O transport, not a service. `websocket` is correct; `slack` is not.
66
70
 
67
- Create the file at `src/cli/spells/connectors/<name>.ts`.
68
-
69
- Follow this template, using `github-cli.ts` as the reference implementation:
70
-
71
- ```typescript
72
- /**
73
- * <Name> Workflow Connector
74
- *
75
- * <Description>
76
- *
77
- * Actions: <comma-separated action names>
78
- */
79
-
80
- import type {
81
- SpellConnector,
82
- ConnectorAction,
83
- ConnectorOutput,
84
- ConnectorCapability,
85
- } from '../types/spell-connector.types.js';
86
-
87
- export type <Name>Action = '<action-1>' | '<action-2>';
88
-
89
- export const VALID_ACTIONS: readonly <Name>Action[] = [
90
- '<action-1>', '<action-2>',
91
- ];
71
+ ### Step 2: Disclose Permissions
92
72
 
93
- async function execute<Action1>(
94
- params: Record<string, unknown>,
95
- start: number,
96
- ): Promise<ConnectorOutput> {
97
- // Implementation
98
- return { success: true, data: { /* result */ }, duration: Date.now() - start };
99
- }
100
-
101
- export function validate<Name>Action(
102
- action: string,
103
- params: Record<string, unknown>,
104
- ): string[] {
105
- const errors: string[] = [];
106
- if (!action || !VALID_ACTIONS.includes(action as <Name>Action)) {
107
- errors.push(`action must be one of: ${VALID_ACTIONS.join(', ')}`);
108
- return errors;
109
- }
110
- switch (action) {
111
- case '<action-1>':
112
- if (!params.<requiredParam>) errors.push('<action-1> requires <requiredParam>');
113
- break;
114
- }
115
- return errors;
116
- }
117
-
118
- const ACTIONS: ConnectorAction[] = [
119
- {
120
- name: '<action-1>',
121
- description: '<action description>',
122
- inputSchema: {
123
- type: 'object',
124
- properties: {
125
- // Define input params with types and descriptions
126
- },
127
- required: ['<required-param>'],
128
- },
129
- outputSchema: {
130
- type: 'object',
131
- properties: {
132
- // Define output fields
133
- },
134
- },
135
- },
136
- ];
73
+ Before generating any source, surface the connector's capability profile to the user. Capabilities like `shell`, `fs:write`, `credentials`, `agent`, `net`, and `browser` carry specific risk classifications and required warning text. **See [../spell-builder/permissions.md](../spell-builder/permissions.md)** for the levels (`readonly`/`standard`/`elevated`/`autonomous`), risk classes (`[SAFE]`/`[SENSITIVE]`/`[DESTRUCTIVE]`), and the warning text to display per capability.
137
74
 
138
- export const <name>Connector: SpellConnector = {
139
- name: '<name>',
140
- description: '<Description>',
141
- version: '<version>',
142
- capabilities: [<capabilities>] as readonly ConnectorCapability[],
143
-
144
- async initialize(config: Record<string, unknown>): Promise<void> {
145
- // Validate prerequisites (CLI tools, auth, API keys, etc.)
146
- },
147
-
148
- async dispose(): Promise<void> {
149
- // Clean up connections/resources
150
- },
151
-
152
- async execute(action: string, params: Record<string, unknown>): Promise<ConnectorOutput> {
153
- const start = Date.now();
154
- const errors = validate<Name>Action(action, params);
155
- if (errors.length > 0) {
156
- return { success: false, data: {}, error: errors.join('; '), duration: Date.now() - start };
157
- }
158
- switch (action) {
159
- case '<action-1>':
160
- return execute<Action1>(params, start);
161
- default:
162
- return { success: false, data: {}, error: `Unknown action: ${action}`, duration: Date.now() - start };
163
- }
164
- },
165
-
166
- listActions(): ConnectorAction[] {
167
- return ACTIONS;
168
- },
169
- };
170
- ```
75
+ ### Step 3: Generate Connector Source + Test
171
76
 
172
- ### Step 3: Generate Connector Test
77
+ Use the full scaffold in [templates/connector.md](templates/connector.md). It implements the `SpellConnector` interface with all required pieces:
173
78
 
174
- Create at `tests/packages/spells/connectors/<name>.test.ts`:
79
+ - `name`, `description`, `version`, `capabilities`
80
+ - Lifecycle methods: `initialize` and `dispose`
81
+ - `execute(action, params)` dispatcher
82
+ - `listActions()` schema reporter
83
+ - Per-action validation function
84
+ - Vitest test file with mocks
175
85
 
176
- ```typescript
177
- import { describe, it, expect, vi, beforeEach } from 'vitest';
178
- import { <name>Connector, validate<Name>Action } from
179
- '../../../../src/cli/spells/connectors/<name>.js';
180
-
181
- describe('<name>Connector', () => {
182
- describe('metadata', () => {
183
- it('has required properties', () => {
184
- expect(<name>Connector.name).toBe('<name>');
185
- expect(<name>Connector.description).toBeTruthy();
186
- expect(<name>Connector.version).toMatch(/^\d+\.\d+\.\d+$/);
187
- expect(<name>Connector.capabilities.length).toBeGreaterThan(0);
188
- });
189
-
190
- it('lists actions with schemas', () => {
191
- const actions = <name>Connector.listActions();
192
- expect(actions.length).toBeGreaterThan(0);
193
- for (const action of actions) {
194
- expect(action.name).toBeTruthy();
195
- expect(action.description).toBeTruthy();
196
- expect(action.inputSchema).toBeDefined();
197
- expect(action.outputSchema).toBeDefined();
198
- }
199
- });
200
- });
201
-
202
- describe('validation', () => {
203
- it('rejects unknown actions', () => {
204
- const errors = validate<Name>Action('unknown', {});
205
- expect(errors.length).toBeGreaterThan(0);
206
- });
207
-
208
- it('validates required params for <action-1>', () => {
209
- const errors = validate<Name>Action('<action-1>', {});
210
- expect(errors.length).toBeGreaterThan(0);
211
- });
212
-
213
- it('accepts valid params for <action-1>', () => {
214
- const errors = validate<Name>Action('<action-1>', { <requiredParam>: 'value' });
215
- expect(errors).toEqual([]);
216
- });
217
- });
218
-
219
- describe('execute', () => {
220
- it('returns error for unknown action', async () => {
221
- const result = await <name>Connector.execute('unknown', {});
222
- expect(result.success).toBe(false);
223
- expect(result.error).toContain('Unknown action');
224
- });
225
-
226
- // Add per-action execution tests with mocked externals
227
- });
228
-
229
- describe('lifecycle', () => {
230
- it('initializes without error', async () => {
231
- // Mock prerequisites as available
232
- await expect(<name>Connector.initialize({})).resolves.not.toThrow();
233
- });
234
-
235
- it('disposes without error', async () => {
236
- await expect(<name>Connector.dispose()).resolves.not.toThrow();
237
- });
238
- });
239
- });
240
- ```
86
+ The generated test file lives at `src/cli/__tests__/spells/<name>.test.ts`.
241
87
 
242
88
  ### Step 4: Register the Connector
243
89
 
244
- Add to `src/cli/spells/connectors/index.ts`:
90
+ Add to `src/cli/spells/connectors/index.ts` so the engine discovers it:
245
91
 
246
92
  ```typescript
247
93
  import { <name>Connector } from './<name>.js';
248
-
249
94
  export { <name>Connector };
250
95
 
251
- // Add to the builtinConnectors array:
252
96
  export const builtinConnectors: SpellConnector[] = [
253
97
  httpConnector,
254
98
  githubCliConnector,
@@ -257,7 +101,7 @@ export const builtinConnectors: SpellConnector[] = [
257
101
  ];
258
102
  ```
259
103
 
260
- ### Step 5: Example Workflow YAML
104
+ ### Step 5: Example Spell YAML
261
105
 
262
106
  ```yaml
263
107
  name: example-with-<name>
@@ -287,8 +131,6 @@ steps:
287
131
 
288
132
  ### Step 1: Gather Requirements
289
133
 
290
- Ask the user for:
291
-
292
134
  | Field | Required | Example |
293
135
  |-------|----------|---------|
294
136
  | **Type** | Yes (kebab-case) | `transform`, `notify`, `validate-schema` |
@@ -299,250 +141,23 @@ Ask the user for:
299
141
  | **MoFlo level** | No (default `none`) | `none`, `memory`, `hooks`, `full`, `recursive` |
300
142
  | **Prerequisites** | No | External CLI tools needed |
301
143
 
302
- #### REQUIRED: Permission Disclosure
303
-
304
- **After gathering capabilities, you MUST display the permission implications to the user.** Classify and show:
144
+ ### Step 2: Disclose Permissions
305
145
 
306
- - **Permission level** derived from capabilities:
307
- - `readonly` (Read, Glob, Grep) — no `shell`, `fs:write`, `agent`, `net`, `browser` capabilities
308
- - `standard` (Edit, Write, Read, Glob, Grep) — has `fs:write` or `agent` but not `shell`/`browser`
309
- - `elevated` (Edit, Write, Bash, Read, Glob, Grep) — has `shell` or `browser`
310
- - `autonomous` — requires explicit opt-in, never auto-derived
146
+ After gathering capabilities, you MUST display the permission implications to the user — same rules as the connector path. **See [../spell-builder/permissions.md](../spell-builder/permissions.md)** for the format and per-capability warnings. Steps using destructive capabilities will require user acceptance before first run via the spell-wide dry-run report.
311
147
 
312
- - **Risk classification:**
313
- - **[SAFE]** — `fs:read`, `memory` only
314
- - **[SENSITIVE]** — `agent`, `net`, `browser`
315
- - **[DESTRUCTIVE]** — `shell`, `fs:write`, `browser:evaluate`, `credentials`
148
+ ### Step 3: Generate Step Command Source + Test
316
149
 
317
- - **Specific warnings** for each destructive/sensitive capability:
318
- - `shell`: "Can execute arbitrary shell commands (rm, git push, etc.)"
319
- - `fs:write`: "Can create, overwrite, or delete files on disk"
320
- - `credentials`: "Can access stored secrets and API keys"
321
- - `agent`: "Can spawn autonomous Claude sub-agents"
322
- - `net`: "Can make network requests to external services"
150
+ Use the full scaffold in [templates/step-command.md](templates/step-command.md). It implements the `StepCommand<TConfig>` interface with all required pieces:
323
151
 
324
- **Example display:**
152
+ - `type`, `description`, `capabilities`, `defaultMofloLevel`
153
+ - `configSchema` (JSONSchema for runtime validation)
154
+ - `validate(config, context)` and `execute(config, context)` methods
155
+ - `describeOutputs()` for downstream variable references
156
+ - Optional `preflight` block — see [../spell-builder/preflight.md](../spell-builder/preflight.md) for `reason` copywriting rules
157
+ - Optional `rollback(config, context)` for failure cleanup
158
+ - Vitest test file with `mockContext`
325
159
 
326
- ```
327
- Step command "deploy" capabilities:
328
- [DESTRUCTIVE]
329
- Permission level: elevated
330
- Capabilities: shell, fs:write, fs:read
331
- Warnings:
332
- !! shell: Can execute arbitrary shell commands (rm, git push, etc.)
333
- !! fs:write: Can create, overwrite, or delete files on disk
334
-
335
- Steps using this command will require user acceptance before first run.
336
- ```
337
-
338
- ### Step 2: Generate Step Command Source
339
-
340
- Create at `src/cli/spells/commands/<type>-command.ts`.
341
-
342
- Follow this template, using `bash-command.ts` as the reference implementation:
343
-
344
- ```typescript
345
- /**
346
- * <Type> Step Command — <description>.
347
- */
348
-
349
- import type {
350
- StepCommand,
351
- StepConfig,
352
- StepOutput,
353
- CastingContext,
354
- ValidationResult,
355
- OutputDescriptor,
356
- JSONSchema,
357
- StepCapability,
358
- } from '../types/step-command.types.js';
359
-
360
- /** Typed config for the <type> step command. */
361
- export interface <Type>StepConfig extends StepConfig {
362
- readonly <field1>: <type1>;
363
- readonly <field2>?: <type2>;
364
- }
365
-
366
- export const <type>Command: StepCommand<<Type>StepConfig> = {
367
- type: '<type>',
368
- description: '<Description>',
369
- capabilities: [
370
- // { type: 'fs:read' },
371
- ] as readonly StepCapability[],
372
- defaultMofloLevel: 'none',
373
- configSchema: {
374
- type: 'object',
375
- properties: {
376
- <field1>: { type: '<json-type>', description: '<Field description>' },
377
- <field2>: { type: '<json-type>', description: '<Field description>' },
378
- },
379
- required: ['<field1>'],
380
- } satisfies JSONSchema,
381
-
382
- validate(config: <Type>StepConfig): ValidationResult {
383
- const errors = [];
384
- if (!config.<field1> || typeof config.<field1> !== '<expected-type>') {
385
- errors.push({ path: '<field1>', message: '<field1> is required and must be a <expected-type>' });
386
- }
387
- return { valid: errors.length === 0, errors };
388
- },
389
-
390
- async execute(config: <Type>StepConfig, context: CastingContext): Promise<StepOutput> {
391
- const start = Date.now();
392
- try {
393
- // Implementation here
394
- const result = {}; // compute result
395
-
396
- return {
397
- success: true,
398
- data: { result },
399
- duration: Date.now() - start,
400
- };
401
- } catch (err) {
402
- return {
403
- success: false,
404
- data: {},
405
- error: err instanceof Error ? err.message : String(err),
406
- duration: Date.now() - start,
407
- };
408
- }
409
- },
410
-
411
- describeOutputs(): OutputDescriptor[] {
412
- return [
413
- { name: 'result', type: 'object', description: 'The computed result' },
414
- ];
415
- },
416
-
417
- // Optional: runtime preflight checks — run BEFORE any step executes.
418
- // Use for validating runtime state (issue open, service reachable, etc).
419
- // CRITICAL: the `reason` string IS the message the end user sees.
420
- // Write it in plain English. State the problem AND the fix. No tool
421
- // jargon, no exit codes, no internal identifiers.
422
- //
423
- // Default severity is 'fatal' (abort on failure). Set severity: 'warning'
424
- // + resolutions when the user can safely choose how to proceed; in
425
- // interactive runs they'll be prompted, in non-interactive runs warnings
426
- // behave like fatals.
427
- //
428
- // preflight: [
429
- // {
430
- // name: '<service> reachable',
431
- // severity: 'fatal',
432
- // check: async (config, ctx) => {
433
- // const ok = await ping(config.endpoint);
434
- // if (ok) return { passed: true };
435
- // return {
436
- // passed: false,
437
- // reason: `Can't reach ${config.endpoint}. Check your network connection or the service URL in your spell config.`,
438
- // };
439
- // },
440
- // },
441
- // {
442
- // name: 'local cache fresh',
443
- // severity: 'warning',
444
- // resolutions: [
445
- // { label: 'Refresh the cache now', command: '<type>-cli cache refresh' },
446
- // { label: 'Continue with stale cache' },
447
- // ],
448
- // check: async (config) => {
449
- // const stale = await isCacheStale(config.endpoint);
450
- // return stale
451
- // ? { passed: false, reason: 'Your local cache is more than 24 hours old and may produce outdated results.' }
452
- // : { passed: true };
453
- // },
454
- // },
455
- // ],
456
-
457
- // Optional: rollback on failure
458
- // async rollback(config, context) { /* undo side effects */ },
459
- };
460
- ```
461
-
462
- Alternatively, use the `createStepCommand()` factory from `src/cli/spells/commands/create-step-command.ts` for compile-time type safety.
463
-
464
- #### Preflight `reason` strings — write for humans
465
-
466
- When your step declares `preflight` checks, the `reason` string returned on failure is shown verbatim to end users as the error message. Treat it as user-facing copy:
467
-
468
- - Plain English, no command names, exit codes, or internal identifiers.
469
- - State BOTH the problem and the fix.
470
- - Assume a non-technical reader.
471
-
472
- Good: `"You're not signed in to GitHub. Run: gh auth login"`
473
- Bad: `"gh auth status exited with code 1"` — leaks implementation detail
474
- Bad: `"auth check failed"` — tells the user nothing actionable
475
-
476
- ### Step 3: Generate Step Command Test
477
-
478
- Create at `tests/packages/spells/commands/<type>-command.test.ts`:
479
-
480
- ```typescript
481
- import { describe, it, expect, vi } from 'vitest';
482
- import { <type>Command } from
483
- '../../../../src/cli/spells/commands/<type>-command.js';
484
- import type { CastingContext } from
485
- '../../../../src/cli/spells/types/step-command.types.js';
486
-
487
- const mockContext: CastingContext = {
488
- variables: {},
489
- args: {},
490
- credentials: { get: vi.fn(), has: vi.fn() },
491
- memory: { read: vi.fn(), write: vi.fn(), search: vi.fn() },
492
- taskId: 'test-task',
493
- spellId: 'test-spell',
494
- stepIndex: 0,
495
- };
496
-
497
- describe('<type>Command', () => {
498
- describe('metadata', () => {
499
- it('has required properties', () => {
500
- expect(<type>Command.type).toBe('<type>');
501
- expect(<type>Command.description).toBeTruthy();
502
- expect(<type>Command.configSchema).toBeDefined();
503
- expect(<type>Command.configSchema.required).toContain('<field1>');
504
- });
505
- });
506
-
507
- describe('validate', () => {
508
- it('rejects missing required fields', () => {
509
- const result = <type>Command.validate({} as any, mockContext);
510
- expect(result.valid).toBe(false);
511
- expect(result.errors.length).toBeGreaterThan(0);
512
- });
513
-
514
- it('accepts valid config', () => {
515
- const result = <type>Command.validate(
516
- { <field1>: '<valid-value>' } as any,
517
- mockContext,
518
- );
519
- expect(result.valid).toBe(true);
520
- });
521
- });
522
-
523
- describe('execute', () => {
524
- it('succeeds with valid config', async () => {
525
- const result = await <type>Command.execute(
526
- { <field1>: '<valid-value>' } as any,
527
- mockContext,
528
- );
529
- expect(result.success).toBe(true);
530
- expect(result.duration).toBeGreaterThanOrEqual(0);
531
- });
532
- });
533
-
534
- describe('describeOutputs', () => {
535
- it('returns output descriptors', () => {
536
- const outputs = <type>Command.describeOutputs();
537
- expect(outputs.length).toBeGreaterThan(0);
538
- for (const output of outputs) {
539
- expect(output.name).toBeTruthy();
540
- expect(output.type).toBeTruthy();
541
- }
542
- });
543
- });
544
- });
545
- ```
160
+ For compile-time type safety on the config, prefer the `createStepCommand()` factory from `src/cli/spells/commands/create-step-command.ts`.
546
161
 
547
162
  ### Step 4: Register the Step Command
548
163
 
@@ -550,11 +165,9 @@ Add to `src/cli/spells/commands/index.ts`:
550
165
 
551
166
  ```typescript
552
167
  import { <type>Command } from './<type>-command.js';
553
-
554
168
  export { <type>Command };
555
169
  export type { <Type>StepConfig } from './<type>-command.js';
556
170
 
557
- // Add to the builtinCommands array:
558
171
  export const builtinCommands: readonly StepCommand[] = [
559
172
  agentCommand,
560
173
  bashCommand,
@@ -563,7 +176,7 @@ export const builtinCommands: readonly StepCommand[] = [
563
176
  ];
564
177
  ```
565
178
 
566
- ### Step 5: Example Workflow YAML
179
+ ### Step 5: Example Spell YAML
567
180
 
568
181
  ```yaml
569
182
  name: example-with-<type>
@@ -596,4 +209,4 @@ steps:
596
209
 
597
210
  ### Related Skills
598
211
 
599
- - [/spell-builder](../spell-builder/) (#240) — composes connectors and steps into spell definitions; references this skill when a needed connector doesn't exist
212
+ - [/spell-builder](../spell-builder/) — composes connectors and steps into spell definitions; references this skill when a needed connector doesn't exist