@zibby/skills 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Neutral tracker types — the shared contract every tracker adapter implements.
3
+ * ============================================================================
4
+ *
5
+ * This is the "neutrality payoff": a pipeline reads/writes ANY issue tracker
6
+ * (Jira, Linear, GitHub Issues, Plane) through ONE interface, so a workflow
7
+ * template never hard-codes a provider's REST shape.
8
+ *
9
+ * Design rule (from the build plan, "中立抽象最小版"):
10
+ * A field/method belongs in this contract IFF Jira AND GitHub AND Linear all
11
+ * have it AND the semantics line up. Provider-only data (Jira sprint /
12
+ * story-point, Linear cycle, GitHub milestone, Plane sequence_id) lives in
13
+ * `_raw` — never promoted to a top-level neutral field. Resisting that
14
+ * promotion is what keeps this from becoming "a Jira layer with a hat on".
15
+ *
16
+ * Field names are deliberately aligned to the OpenAI Symphony §4 domain model
17
+ * (id / key / title / body / state / assignee / url) so a future Symphony
18
+ * compatibility layer is additive, not a rewrite.
19
+ *
20
+ * STATE MODEL — the one piece of real cleverness:
21
+ * `state` is the RAW provider status name ("In Progress", "测试", "Done",
22
+ * "started"). It is what you WRITE BACK with — provider workflows reject made-
23
+ * up names, so the pipeline must echo the tracker's own vocabulary.
24
+ * `stateCategory` is the NORMALIZED bucket the pipeline BRANCHES on. Five
25
+ * buckets, no more:
26
+ * - 'todo' not started yet (Jira `new`, Linear backlog/unstarted,
27
+ * GitHub open w/o a progress label, Plane backlog/unstarted)
28
+ * - 'in_progress' actively being worked (Jira `indeterminate`,
29
+ * Linear started, Plane started, GitHub open w/ an
30
+ * in-progress label)
31
+ * - 'done' terminal/closed (Jira `done`, Linear completed/canceled,
32
+ * Plane completed/cancelled, GitHub closed)
33
+ * - 'blocked' explicitly blocked — NOT a native group in any provider;
34
+ * only ever derived from a status NAME match
35
+ * (blocked / on hold / waiting / stuck). Adapters surface
36
+ * it best-effort; absence of 'blocked' is not a bug.
37
+ * - 'unknown' could not classify (missing/unrecognized state)
38
+ * Keeping the pair separate is the whole trick: pipeline logic on the bucket,
39
+ * write-back on the raw name. Don't collapse them.
40
+ */
41
+ /**
42
+ * @typedef {'todo'|'in_progress'|'done'|'blocked'|'unknown'} StateCategory
43
+ * Normalized 5-bucket state. The pipeline branches on this; never on `state`.
44
+ */
45
+ /**
46
+ * A tracker ticket/issue projected onto the neutral model.
47
+ *
48
+ * @typedef {Object} NeutralTicket
49
+ * @property {string} id
50
+ * Stable provider-internal id. Jira: issue id (numeric string). Linear: uuid.
51
+ * GitHub: issue number as a string. Plane: work-item uuid. Opaque — pass it
52
+ * back to the same adapter; do not parse it.
53
+ * @property {string} key
54
+ * Human-facing reference. Jira: `PROJ-123`. Linear: `ENG-12`. GitHub:
55
+ * `owner/repo#123`. Plane: `PROJ-42` (or the uuid if the identifier is
56
+ * unknown). This is what shows up in PR titles / comments.
57
+ * @property {string} title
58
+ * Short summary line (Symphony §4 `title`).
59
+ * @property {string} body
60
+ * Full description as plain text / markdown (Symphony §4 `body`). ADF/HTML is
61
+ * flattened by the adapter; never raw ADF or raw HTML here.
62
+ * @property {string|null} state
63
+ * RAW provider status NAME, exactly as the tracker spells it ("In Progress",
64
+ * "Done", "started", "测试"). Use this for write-back (transition). `null`
65
+ * when the provider/issue has no resolvable status.
66
+ * @property {StateCategory} stateCategory
67
+ * Normalized bucket — what the pipeline branches on.
68
+ * @property {string|null} assignee
69
+ * Single human-readable assignee (display name or login). `null` if
70
+ * unassigned. Multi-assignee providers (GitHub, Plane) collapse to the first;
71
+ * the full list, if any, lives in `_raw`.
72
+ * @property {string|null} url
73
+ * Canonical web URL of the ticket, or `null` if the adapter can't derive one.
74
+ * @property {Object} _raw
75
+ * Escape hatch — the untouched provider payload. Read provider-specific data
76
+ * (sprint, story points, cycle, milestone, sequence_id, labels…) from here.
77
+ * NEVER promote a `_raw` field to a top-level neutral field.
78
+ */
79
+ /**
80
+ * A single comment on a ticket, projected onto the neutral model.
81
+ *
82
+ * @typedef {Object} NeutralComment
83
+ * @property {string} id Provider comment id (opaque).
84
+ * @property {string} author Human-readable author (display name/login).
85
+ * @property {string} body Comment text as plain text / markdown
86
+ * (ADF/HTML flattened).
87
+ * @property {string|null} createdAt ISO-8601 creation timestamp, if available.
88
+ * @property {string|null} updatedAt ISO-8601 update timestamp, if available.
89
+ * @property {Object} [_raw] Untouched provider comment payload.
90
+ */
91
+ /**
92
+ * Options accepted by {@link TrackerAdapter.listCandidates}. Every field is
93
+ * optional; what each provider honors differs (documented per adapter). The
94
+ * shared minimum is "give me a bounded, newest-first slab of work to consider".
95
+ *
96
+ * @typedef {Object} ListCandidatesOptions
97
+ * @property {string} [query]
98
+ * Provider-native query. Jira: a JQL string. GitHub: free-text issue search
99
+ * (or used with `labels`). Linear/Plane: ignored in favor of structured
100
+ * filters below. Adapters that can't honor it ignore it.
101
+ * @property {string|string[]} [labels] Restrict to issues carrying label(s).
102
+ * @property {string} [state] Provider state filter (raw name or, for
103
+ * GitHub, open|closed|all).
104
+ * @property {string} [updatedAfter] ISO-8601 polling cursor; only issues
105
+ * updated at/after this.
106
+ * @property {number} [limit] Max tickets to return (bounded).
107
+ * @property {string} [cursor] Opaque pagination cursor (Plane).
108
+ * @property {Object} [ctx] Provider scope/context (e.g. GitHub
109
+ * {owner, repo}; Plane {workspaceSlug,
110
+ * projectId}). Passed through verbatim.
111
+ */
112
+ /**
113
+ * The neutral tracker interface — the 6-method MINIMUM contract.
114
+ *
115
+ * Three READ methods + three WRITE methods + identity metadata. Each concrete
116
+ * adapter (jira / linear / github / plane) implements exactly these. Anything a
117
+ * single provider can do that the others can't does NOT get a 7th method — it
118
+ * stays accessible only via that provider's own skill tools / the `_raw` hatch.
119
+ *
120
+ * @typedef {Object} TrackerAdapter
121
+ * @property {string} id
122
+ * Provider id: 'jira' | 'linear' | 'github' | 'plane'.
123
+ *
124
+ * @property {(opts?: ListCandidatesOptions) => Promise<NeutralTicket[]>} listCandidates
125
+ * READ. Return a bounded, newest-first set of tickets to consider for work.
126
+ *
127
+ * @property {(key: string, ctx?: Object) => Promise<NeutralTicket|null>} getTicket
128
+ * READ. Fetch one ticket by its `key` (or id). `null` if not found.
129
+ *
130
+ * @property {(key: string, ctx?: Object) => Promise<NeutralComment[]>} getComments
131
+ * READ. Fetch the comment thread (newest first).
132
+ *
133
+ * @property {(key: string, body: string, ctx?: Object) => Promise<{ok: boolean, id?: string|null, error?: string}>} addComment
134
+ * WRITE. Add a comment. `body` is plain text / markdown; the adapter handles
135
+ * provider encoding (Jira ADF, Plane HTML).
136
+ *
137
+ * @property {(key: string, targetStateName: string, ctx?: Object) => Promise<{ok: boolean, stateAfter?: string|null, stateCategoryAfter?: StateCategory, error?: string}>} transition
138
+ * WRITE. Move the ticket to a target state, addressed by its RAW name
139
+ * (fuzzy-matched per provider). Jira performs a workflow transition; Linear /
140
+ * Plane PATCH the state directly; GitHub maps to open/close (+ a labels
141
+ * convention — see github-adapter). NOT every target name is reachable on
142
+ * every provider; the result carries `ok:false` + `error` when it isn't.
143
+ *
144
+ * @property {(key: string, prUrl: string, title?: string, ctx?: Object) => Promise<{ok: boolean, via?: string, error?: string}>} linkPullRequest
145
+ * WRITE. Associate a PR URL with the ticket. Native where possible (Jira
146
+ * remote-link, Linear attachment); a comment everywhere else. `via` reports
147
+ * which path was used ('remotelink' | 'attachment' | 'comment').
148
+ */
149
+ /** @type {StateCategory[]} */
150
+ export const TRACKER_STATE_CATEGORIES: StateCategory[];
151
+ /**
152
+ * Normalized 5-bucket state. The pipeline branches on this; never on `state`.
153
+ */
154
+ export type StateCategory = "todo" | "in_progress" | "done" | "blocked" | "unknown";
155
+ /**
156
+ * A tracker ticket/issue projected onto the neutral model.
157
+ */
158
+ export type NeutralTicket = {
159
+ /**
160
+ * Stable provider-internal id. Jira: issue id (numeric string). Linear: uuid.
161
+ * GitHub: issue number as a string. Plane: work-item uuid. Opaque — pass it
162
+ * back to the same adapter; do not parse it.
163
+ */
164
+ id: string;
165
+ /**
166
+ * Human-facing reference. Jira: `PROJ-123`. Linear: `ENG-12`. GitHub:
167
+ * `owner/repo#123`. Plane: `PROJ-42` (or the uuid if the identifier is
168
+ * unknown). This is what shows up in PR titles / comments.
169
+ */
170
+ key: string;
171
+ /**
172
+ * Short summary line (Symphony §4 `title`).
173
+ */
174
+ title: string;
175
+ /**
176
+ * Full description as plain text / markdown (Symphony §4 `body`). ADF/HTML is
177
+ * flattened by the adapter; never raw ADF or raw HTML here.
178
+ */
179
+ body: string;
180
+ /**
181
+ * RAW provider status NAME, exactly as the tracker spells it ("In Progress",
182
+ * "Done", "started", "测试"). Use this for write-back (transition). `null`
183
+ * when the provider/issue has no resolvable status.
184
+ */
185
+ state: string | null;
186
+ /**
187
+ * Normalized bucket — what the pipeline branches on.
188
+ */
189
+ stateCategory: StateCategory;
190
+ /**
191
+ * Single human-readable assignee (display name or login). `null` if
192
+ * unassigned. Multi-assignee providers (GitHub, Plane) collapse to the first;
193
+ * the full list, if any, lives in `_raw`.
194
+ */
195
+ assignee: string | null;
196
+ /**
197
+ * Canonical web URL of the ticket, or `null` if the adapter can't derive one.
198
+ */
199
+ url: string | null;
200
+ /**
201
+ * Escape hatch — the untouched provider payload. Read provider-specific data
202
+ * (sprint, story points, cycle, milestone, sequence_id, labels…) from here.
203
+ * NEVER promote a `_raw` field to a top-level neutral field.
204
+ */
205
+ _raw: any;
206
+ };
207
+ /**
208
+ * A single comment on a ticket, projected onto the neutral model.
209
+ */
210
+ export type NeutralComment = {
211
+ /**
212
+ * Provider comment id (opaque).
213
+ */
214
+ id: string;
215
+ /**
216
+ * Human-readable author (display name/login).
217
+ */
218
+ author: string;
219
+ /**
220
+ * Comment text as plain text / markdown
221
+ * (ADF/HTML flattened).
222
+ */
223
+ body: string;
224
+ /**
225
+ * ISO-8601 creation timestamp, if available.
226
+ */
227
+ createdAt: string | null;
228
+ /**
229
+ * ISO-8601 update timestamp, if available.
230
+ */
231
+ updatedAt: string | null;
232
+ /**
233
+ * Untouched provider comment payload.
234
+ */
235
+ _raw?: any;
236
+ };
237
+ /**
238
+ * Options accepted by {@link TrackerAdapter.listCandidates}. Every field is
239
+ * optional; what each provider honors differs (documented per adapter). The
240
+ * shared minimum is "give me a bounded, newest-first slab of work to consider".
241
+ */
242
+ export type ListCandidatesOptions = {
243
+ /**
244
+ * Provider-native query. Jira: a JQL string. GitHub: free-text issue search
245
+ * (or used with `labels`). Linear/Plane: ignored in favor of structured
246
+ * filters below. Adapters that can't honor it ignore it.
247
+ */
248
+ query?: string;
249
+ /**
250
+ * Restrict to issues carrying label(s).
251
+ */
252
+ labels?: string | string[];
253
+ /**
254
+ * Provider state filter (raw name or, for
255
+ * GitHub, open|closed|all).
256
+ */
257
+ state?: string;
258
+ /**
259
+ * ISO-8601 polling cursor; only issues
260
+ * updated at/after this.
261
+ */
262
+ updatedAfter?: string;
263
+ /**
264
+ * Max tickets to return (bounded).
265
+ */
266
+ limit?: number;
267
+ /**
268
+ * Opaque pagination cursor (Plane).
269
+ */
270
+ cursor?: string;
271
+ /**
272
+ * Provider scope/context (e.g. GitHub
273
+ * {owner, repo}; Plane {workspaceSlug,
274
+ * projectId}). Passed through verbatim.
275
+ */
276
+ ctx?: any;
277
+ };
278
+ /**
279
+ * The neutral tracker interface — the 6-method MINIMUM contract.
280
+ *
281
+ * Three READ methods + three WRITE methods + identity metadata. Each concrete
282
+ * adapter (jira / linear / github / plane) implements exactly these. Anything a
283
+ * single provider can do that the others can't does NOT get a 7th method — it
284
+ * stays accessible only via that provider's own skill tools / the `_raw` hatch.
285
+ */
286
+ export type TrackerAdapter = {
287
+ /**
288
+ * Provider id: 'jira' | 'linear' | 'github' | 'plane'.
289
+ */
290
+ id: string;
291
+ /**
292
+ * READ. Return a bounded, newest-first set of tickets to consider for work.
293
+ */
294
+ listCandidates: (opts?: ListCandidatesOptions) => Promise<NeutralTicket[]>;
295
+ /**
296
+ * READ. Fetch one ticket by its `key` (or id). `null` if not found.
297
+ */
298
+ getTicket: (key: string, ctx?: any) => Promise<NeutralTicket | null>;
299
+ /**
300
+ * READ. Fetch the comment thread (newest first).
301
+ */
302
+ getComments: (key: string, ctx?: any) => Promise<NeutralComment[]>;
303
+ /**
304
+ * WRITE. Add a comment. `body` is plain text / markdown; the adapter handles
305
+ * provider encoding (Jira ADF, Plane HTML).
306
+ */
307
+ addComment: (key: string, body: string, ctx?: any) => Promise<{
308
+ ok: boolean;
309
+ id?: string | null;
310
+ error?: string;
311
+ }>;
312
+ /**
313
+ * WRITE. Move the ticket to a target state, addressed by its RAW name
314
+ * (fuzzy-matched per provider). Jira performs a workflow transition; Linear /
315
+ * Plane PATCH the state directly; GitHub maps to open/close (+ a labels
316
+ * convention — see github-adapter). NOT every target name is reachable on
317
+ * every provider; the result carries `ok:false` + `error` when it isn't.
318
+ */
319
+ transition: (key: string, targetStateName: string, ctx?: any) => Promise<{
320
+ ok: boolean;
321
+ stateAfter?: string | null;
322
+ stateCategoryAfter?: StateCategory;
323
+ error?: string;
324
+ }>;
325
+ /**
326
+ * WRITE. Associate a PR URL with the ticket. Native where possible (Jira
327
+ * remote-link, Linear attachment); a comment everywhere else. `via` reports
328
+ * which path was used ('remotelink' | 'attachment' | 'comment').
329
+ */
330
+ linkPullRequest: (key: string, prUrl: string, title?: string, ctx?: any) => Promise<{
331
+ ok: boolean;
332
+ via?: string;
333
+ error?: string;
334
+ }>;
335
+ };
@@ -0,0 +1,245 @@
1
+ export namespace workflowBuilderSkill {
2
+ export let id: string;
3
+ export let description: string;
4
+ export let envKeys: any[];
5
+ export { PROMPT_FRAGMENT as promptFragment };
6
+ export let tools: ({
7
+ name: string;
8
+ description: string;
9
+ input_schema: {
10
+ type: string;
11
+ properties: {
12
+ name: {
13
+ type: string;
14
+ description: string;
15
+ };
16
+ description: {
17
+ type: string;
18
+ description: string;
19
+ };
20
+ nodes: {
21
+ type: string;
22
+ items: {
23
+ type: string;
24
+ properties: {
25
+ name: {
26
+ type: string;
27
+ description: string;
28
+ };
29
+ description: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ inputFields: {
34
+ type: string;
35
+ items: {
36
+ type: string;
37
+ };
38
+ description: string;
39
+ };
40
+ outputFields: {
41
+ type: string;
42
+ items: {
43
+ type: string;
44
+ };
45
+ description: string;
46
+ };
47
+ };
48
+ required: string[];
49
+ };
50
+ description: string;
51
+ };
52
+ edges: {
53
+ type: string;
54
+ items: {
55
+ type: string;
56
+ properties: {
57
+ from: {
58
+ type: string;
59
+ description: string;
60
+ };
61
+ to: {
62
+ type: string;
63
+ description: string;
64
+ };
65
+ condition: {
66
+ type: string;
67
+ description: string;
68
+ };
69
+ };
70
+ required: string[];
71
+ };
72
+ description: string;
73
+ };
74
+ spec?: undefined;
75
+ workflowName?: undefined;
76
+ nodeName?: undefined;
77
+ inputFields?: undefined;
78
+ outputFields?: undefined;
79
+ projectId?: undefined;
80
+ topic?: undefined;
81
+ };
82
+ required: string[];
83
+ };
84
+ } | {
85
+ name: string;
86
+ description: string;
87
+ input_schema: {
88
+ type: string;
89
+ properties: {
90
+ name: {
91
+ type: string;
92
+ description: string;
93
+ };
94
+ spec: {
95
+ type: string;
96
+ description: string;
97
+ properties: {
98
+ name: {
99
+ type: string;
100
+ };
101
+ description: {
102
+ type: string;
103
+ };
104
+ nodes: {
105
+ type: string;
106
+ items: {
107
+ type: string;
108
+ };
109
+ };
110
+ edges: {
111
+ type: string;
112
+ items: {
113
+ type: string;
114
+ };
115
+ };
116
+ };
117
+ };
118
+ description?: undefined;
119
+ nodes?: undefined;
120
+ edges?: undefined;
121
+ workflowName?: undefined;
122
+ nodeName?: undefined;
123
+ inputFields?: undefined;
124
+ outputFields?: undefined;
125
+ projectId?: undefined;
126
+ topic?: undefined;
127
+ };
128
+ required: string[];
129
+ };
130
+ } | {
131
+ name: string;
132
+ description: string;
133
+ input_schema: {
134
+ type: string;
135
+ properties: {
136
+ workflowName: {
137
+ type: string;
138
+ description: string;
139
+ };
140
+ nodeName: {
141
+ type: string;
142
+ description: string;
143
+ };
144
+ description: {
145
+ type: string;
146
+ description: string;
147
+ };
148
+ inputFields: {
149
+ type: string;
150
+ items: {
151
+ type: string;
152
+ };
153
+ description: string;
154
+ };
155
+ outputFields: {
156
+ type: string;
157
+ items: {
158
+ type: string;
159
+ };
160
+ description: string;
161
+ };
162
+ name?: undefined;
163
+ nodes?: undefined;
164
+ edges?: undefined;
165
+ spec?: undefined;
166
+ projectId?: undefined;
167
+ topic?: undefined;
168
+ };
169
+ required: string[];
170
+ };
171
+ } | {
172
+ name: string;
173
+ description: string;
174
+ input_schema: {
175
+ type: string;
176
+ properties: {
177
+ name: {
178
+ type: string;
179
+ description: string;
180
+ };
181
+ projectId: {
182
+ type: string;
183
+ description: string;
184
+ };
185
+ description?: undefined;
186
+ nodes?: undefined;
187
+ edges?: undefined;
188
+ spec?: undefined;
189
+ workflowName?: undefined;
190
+ nodeName?: undefined;
191
+ inputFields?: undefined;
192
+ outputFields?: undefined;
193
+ topic?: undefined;
194
+ };
195
+ required: string[];
196
+ };
197
+ } | {
198
+ name: string;
199
+ description: string;
200
+ input_schema: {
201
+ type: string;
202
+ properties: {
203
+ name?: undefined;
204
+ description?: undefined;
205
+ nodes?: undefined;
206
+ edges?: undefined;
207
+ spec?: undefined;
208
+ workflowName?: undefined;
209
+ nodeName?: undefined;
210
+ inputFields?: undefined;
211
+ outputFields?: undefined;
212
+ projectId?: undefined;
213
+ topic?: undefined;
214
+ };
215
+ required?: undefined;
216
+ };
217
+ } | {
218
+ name: string;
219
+ description: string;
220
+ input_schema: {
221
+ type: string;
222
+ properties: {
223
+ topic: {
224
+ type: string;
225
+ description: string;
226
+ };
227
+ name?: undefined;
228
+ description?: undefined;
229
+ nodes?: undefined;
230
+ edges?: undefined;
231
+ spec?: undefined;
232
+ workflowName?: undefined;
233
+ nodeName?: undefined;
234
+ inputFields?: undefined;
235
+ outputFields?: undefined;
236
+ projectId?: undefined;
237
+ };
238
+ required?: undefined;
239
+ };
240
+ })[];
241
+ export function handleToolCall(name: any, args: any, context: any): Promise<string>;
242
+ export function resolve(): any;
243
+ }
244
+ declare const PROMPT_FRAGMENT: "## Workflow Builder\n\nYou can help users build custom AI workflows using the Zibby workflow framework.\n\n### What makes Zibby workflows different\nEach node invokes a **real AI agent** (Cursor, Claude, Codex, or Gemini) \u2014 not a thin LLM API wrapper.\nThat means every node has full agent capabilities: tool use, MCP servers (browser, GitHub, Jira, Slack),\nmulti-turn reasoning, and structured output validation via Zod schemas.\n\nKey differentiators:\n- **Agent-powered nodes** \u2014 each step runs a full AI agent (cursor-agent, claude, codex, gemini CLI) with tool access and MCP skills, not a simple chat completion call.\n- **Structured output** \u2014 every node declares a Zod schema; the framework validates and parses the agent's response automatically.\n- **Conditional routing** \u2014 edges can branch on agent-produced fields (e.g., `state.triage.priority === 'critical'`), enabling intelligent decision graphs.\n- **MCP skill injection** \u2014 nodes declare `skills: [SKILLS.BROWSER, SKILLS.GITHUB]` and the framework spins up the right MCP servers automatically.\n- **Deploy anywhere** \u2014 `zibby deploy` pushes to Zibby Cloud with an API trigger; or self-host with `zibby start`.\n- **State accumulation** \u2014 each node's validated output is stored under its name in `state` (e.g., `state.classify_ticket`), so downstream nodes can reference upstream results.\n\n### What is a workflow?\nA directed graph of nodes (AI agent steps) connected by edges. Each node has:\n- `name` \u2014 unique identifier (snake_case)\n- `prompt` \u2014 function that receives state and returns the prompt string sent to the agent\n- `outputSchema` \u2014 Zod schema defining the structured output the agent must return\n- `skills` (optional) \u2014 array of MCP skill IDs the node needs (e.g., `SKILLS.BROWSER`, `SKILLS.GITHUB`)\n- `timeout` (optional) \u2014 max execution time in ms (default: 300000)\n- `model` (optional) \u2014 override the model for this node (e.g., `'claude-opus-4'`)\n\n### File structure\n```\n.zibby/workflows/<name>/\n\u251C\u2500\u2500 graph.mjs \u2014 WorkflowAgent subclass with buildGraph()\n\u251C\u2500\u2500 nodes/\n\u2502 \u251C\u2500\u2500 index.mjs \u2014 barrel export for all nodes\n\u2502 \u2514\u2500\u2500 <node>.mjs \u2014 one file per node\n\u2514\u2500\u2500 workflow.json \u2014 manifest (name, description, triggers)\n```\n\n### Node pattern\n```javascript\nimport { z, SKILLS } from '@zibby/core';\n\nconst OutputSchema = z.object({\n summary: z.string().describe('Brief summary'),\n items: z.array(z.string()).describe('List of extracted items'),\n needsReview: z.boolean().describe('Whether a human should review this'),\n});\n\nexport const myNode = {\n name: 'my_node',\n skills: [SKILLS.GITHUB], // optional \u2014 framework injects MCP servers\n timeout: 120000, // optional \u2014 2 min timeout\n prompt: (state) => \\`You are analyzing a pull request.\n\nInput:\n\\${JSON.stringify(state.input || {}, null, 2)}\n\nReturn a JSON object matching the schema.\\`,\n outputSchema: OutputSchema,\n};\n```\n\n### Graph pattern\n```javascript\nimport { WorkflowAgent, WorkflowGraph } from '@zibby/core';\nimport { classifyNode, routeNode } from './nodes/index.mjs';\n\nexport class MyWorkflow extends WorkflowAgent {\n buildGraph() {\n const graph = new WorkflowGraph();\n graph.addNode('classify', classifyNode);\n graph.addNode('route', routeNode);\n graph.setEntryPoint('classify');\n graph.addEdge('classify', 'route');\n graph.addEdge('route', 'END');\n return graph;\n }\n\n async onComplete(result) {\n // Post-execution hook \u2014 save artifacts, notify, etc.\n console.log('Workflow complete:', result.success);\n }\n}\n```\n\nConditional edges: `graph.addConditionalEdges('node', (state) => state.node.priority === 'high' ? 'escalate' : 'notify')`\n\n### Available SKILLS constants\nImport from `@zibby/core`: `SKILLS.BROWSER`, `SKILLS.MEMORY`, `SKILLS.GITHUB`, `SKILLS.JIRA`, `SKILLS.SLACK`, `SKILLS.RUNNER`\n\n### Deep documentation\nCall `explore_framework_docs` to read detailed framework docs on demand. Use it for:\n- Advanced patterns (middleware, parallel nodes, state schemas)\n- Deployment & cloud triggers\n- CLI commands reference\n- Integration details (Jira, GitHub, etc.)\nCall with no arguments to see all available topics.\n\n### How to use the builder tools\n1. For complex workflows, call `explore_framework_docs(\"custom-workflows\")` first to learn advanced patterns.\n2. Ask the user what their workflow should do, what input it receives, and what steps are needed.\n3. Call `design_workflow` with the structured spec for the user to review.\n4. Once approved, call `build_workflow` to generate real code on disk (uses the configured agent for high-quality code generation).\n5. Remind the user: `zibby start <name>` to test locally, `zibby deploy <name> --project <id>` to deploy to cloud, `zibby logs --workflow <name>` to tail logs.\n\n### Important\n- Each node prompt should be detailed and specific \u2014 tell the AI agent exactly what to do and what format to return.\n- Zod schemas MUST use .describe() on every field so the agent knows what each field means.\n- Node names must be snake_case (e.g., classify_ticket, generate_report).\n- Workflow names must be kebab-case (e.g., ticket-triage, pr-review).\n- State flows through: each node's validated output is stored under its name in state (e.g., state.classify_ticket).\n- Downstream nodes reference upstream outputs in their prompt function (e.g., \\`\\${JSON.stringify(state.classify_ticket, null, 2)}\\`).\n- Nodes can declare skills to get MCP tool access \u2014 the framework handles server lifecycle automatically.";
245
+ export {};