@hailer/mcp 1.1.13 → 1.1.15

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 (176) hide show
  1. package/.claude/.context-watchdog.json +1 -0
  2. package/.claude/.session-checked +1 -0
  3. package/.claude/CLAUDE.md +370 -0
  4. package/.claude/agents/agent-ada-skill-builder.md +94 -0
  5. package/.claude/agents/agent-alejandro-function-fields.md +342 -0
  6. package/.claude/agents/agent-bjorn-config-audit.md +103 -0
  7. package/.claude/agents/agent-builder-agent-creator.md +130 -0
  8. package/.claude/agents/agent-code-simplifier.md +53 -0
  9. package/.claude/agents/agent-dmitri-activity-crud.md +159 -0
  10. package/.claude/agents/agent-giuseppe-app-builder.md +208 -0
  11. package/.claude/agents/agent-gunther-mcp-tools.md +39 -0
  12. package/.claude/agents/agent-helga-workflow-config.md +204 -0
  13. package/.claude/agents/agent-igor-activity-mover-automation.md +125 -0
  14. package/.claude/agents/agent-ingrid-doc-templates.md +261 -0
  15. package/.claude/agents/agent-ivan-monolith.md +154 -0
  16. package/.claude/agents/agent-kenji-data-reader.md +86 -0
  17. package/.claude/agents/agent-lars-code-inspector.md +102 -0
  18. package/.claude/agents/agent-marco-mockup-builder.md +110 -0
  19. package/.claude/agents/agent-marcus-api-documenter.md +323 -0
  20. package/.claude/agents/agent-marketplace-publisher.md +280 -0
  21. package/.claude/agents/agent-marketplace-reviewer.md +309 -0
  22. package/.claude/agents/agent-permissions-handler.md +208 -0
  23. package/.claude/agents/agent-simple-writer.md +48 -0
  24. package/.claude/agents/agent-svetlana-code-review.md +171 -0
  25. package/.claude/agents/agent-tanya-test-runner.md +333 -0
  26. package/.claude/agents/agent-ui-designer.md +100 -0
  27. package/.claude/agents/agent-viktor-sql-insights.md +212 -0
  28. package/.claude/agents/agent-web-search.md +55 -0
  29. package/.claude/agents/agent-yevgeni-discussions.md +45 -0
  30. package/.claude/agents/agent-zara-zapier.md +159 -0
  31. package/.claude/agents/ragnar.md +68 -0
  32. package/.claude/commands/app-squad.md +135 -0
  33. package/.claude/commands/audit-squad.md +158 -0
  34. package/.claude/commands/autoplan.md +563 -0
  35. package/.claude/commands/cleanup-squad.md +98 -0
  36. package/.claude/commands/config-squad.md +106 -0
  37. package/.claude/commands/crud-squad.md +87 -0
  38. package/.claude/commands/data-squad.md +97 -0
  39. package/.claude/commands/debug-squad.md +303 -0
  40. package/.claude/commands/doc-squad.md +65 -0
  41. package/.claude/commands/handoff.md +137 -0
  42. package/.claude/commands/health.md +49 -0
  43. package/.claude/commands/help.md +29 -0
  44. package/.claude/commands/help:agents.md +151 -0
  45. package/.claude/commands/help:commands.md +78 -0
  46. package/.claude/commands/help:faq.md +79 -0
  47. package/.claude/commands/help:plugins.md +50 -0
  48. package/.claude/commands/help:skills.md +93 -0
  49. package/.claude/commands/help:tools.md +75 -0
  50. package/.claude/commands/hotfix-squad.md +112 -0
  51. package/.claude/commands/integration-squad.md +82 -0
  52. package/.claude/commands/janitor-squad.md +167 -0
  53. package/.claude/commands/learn-auto.md +120 -0
  54. package/.claude/commands/learn.md +120 -0
  55. package/.claude/commands/mcp-list.md +27 -0
  56. package/.claude/commands/onboard-squad.md +140 -0
  57. package/.claude/commands/plan-workspace.md +732 -0
  58. package/.claude/commands/prd.md +130 -0
  59. package/.claude/commands/project-status.md +82 -0
  60. package/.claude/commands/publish.md +138 -0
  61. package/.claude/commands/recap.md +69 -0
  62. package/.claude/commands/restore.md +64 -0
  63. package/.claude/commands/review-squad.md +152 -0
  64. package/.claude/commands/save.md +24 -0
  65. package/.claude/commands/stats.md +19 -0
  66. package/.claude/commands/swarm.md +210 -0
  67. package/.claude/commands/tool-builder.md +39 -0
  68. package/.claude/commands/ws-pull.md +44 -0
  69. package/.claude/skills/SDK-activity-patterns/SKILL.md +428 -0
  70. package/.claude/skills/SDK-document-templates/SKILL.md +1033 -0
  71. package/.claude/skills/SDK-function-fields/SKILL.md +542 -0
  72. package/.claude/skills/SDK-generate-skill/SKILL.md +92 -0
  73. package/.claude/skills/SDK-init-skill/SKILL.md +127 -0
  74. package/.claude/skills/SDK-insight-queries/SKILL.md +787 -0
  75. package/.claude/skills/SDK-ws-config-skill/SKILL.md +1139 -0
  76. package/.claude/skills/agent-structure/SKILL.md +98 -0
  77. package/.claude/skills/api-documentation-patterns/SKILL.md +474 -0
  78. package/.claude/skills/chrome-mcp-reference/SKILL.md +370 -0
  79. package/.claude/skills/delegation-routing/SKILL.md +202 -0
  80. package/.claude/skills/frontend-design/SKILL.md +254 -0
  81. package/.claude/skills/hailer-activity-mover/SKILL.md +213 -0
  82. package/.claude/skills/hailer-api-client/SKILL.md +518 -0
  83. package/.claude/skills/hailer-app-builder/SKILL.md +1440 -0
  84. package/.claude/skills/hailer-apps-pictures/SKILL.md +269 -0
  85. package/.claude/skills/hailer-design-system/SKILL.md +231 -0
  86. package/.claude/skills/hailer-monolith-automations/SKILL.md +686 -0
  87. package/.claude/skills/hailer-permissions-system/SKILL.md +121 -0
  88. package/.claude/skills/hailer-project-protocol/SKILL.md +488 -0
  89. package/.claude/skills/hailer-rest-api/SKILL.md +61 -0
  90. package/.claude/skills/hailer-rest-api/hailer-activities.md +184 -0
  91. package/.claude/skills/hailer-rest-api/hailer-admin.md +473 -0
  92. package/.claude/skills/hailer-rest-api/hailer-calendar.md +256 -0
  93. package/.claude/skills/hailer-rest-api/hailer-feed.md +249 -0
  94. package/.claude/skills/hailer-rest-api/hailer-insights.md +195 -0
  95. package/.claude/skills/hailer-rest-api/hailer-messaging.md +276 -0
  96. package/.claude/skills/hailer-rest-api/hailer-workflows.md +283 -0
  97. package/.claude/skills/insight-join-patterns/SKILL.md +174 -0
  98. package/.claude/skills/integration-patterns/SKILL.md +421 -0
  99. package/.claude/skills/json-only-output/SKILL.md +72 -0
  100. package/.claude/skills/lsp-setup/SKILL.md +160 -0
  101. package/.claude/skills/mcp-direct-tools/SKILL.md +153 -0
  102. package/.claude/skills/optional-parameters/SKILL.md +72 -0
  103. package/.claude/skills/publish-hailer-app/SKILL.md +221 -0
  104. package/.claude/skills/testing-patterns/SKILL.md +630 -0
  105. package/.claude/skills/tool-builder/SKILL.md +250 -0
  106. package/.claude/skills/tool-parameter-usage/SKILL.md +126 -0
  107. package/.claude/skills/tool-response-verification/SKILL.md +92 -0
  108. package/.claude/skills/zapier-hailer-patterns/SKILL.md +581 -0
  109. package/.opencode/agent/agent-ada-skill-builder.md +35 -0
  110. package/.opencode/agent/agent-alejandro-function-fields.md +39 -0
  111. package/.opencode/agent/agent-bjorn-config-audit.md +36 -0
  112. package/.opencode/agent/agent-builder-agent-creator.md +39 -0
  113. package/.opencode/agent/agent-code-simplifier.md +31 -0
  114. package/.opencode/agent/agent-dmitri-activity-crud.md +40 -0
  115. package/.opencode/agent/agent-giuseppe-app-builder.md +37 -0
  116. package/.opencode/agent/agent-gunther-mcp-tools.md +39 -0
  117. package/.opencode/agent/agent-helga-workflow-config.md +204 -0
  118. package/.opencode/agent/agent-igor-activity-mover-automation.md +46 -0
  119. package/.opencode/agent/agent-ingrid-doc-templates.md +39 -0
  120. package/.opencode/agent/agent-ivan-monolith.md +46 -0
  121. package/.opencode/agent/agent-kenji-data-reader.md +53 -0
  122. package/.opencode/agent/agent-lars-code-inspector.md +28 -0
  123. package/.opencode/agent/agent-marco-mockup-builder.md +42 -0
  124. package/.opencode/agent/agent-marcus-api-documenter.md +53 -0
  125. package/.opencode/agent/agent-marketplace-publisher.md +44 -0
  126. package/.opencode/agent/agent-marketplace-reviewer.md +42 -0
  127. package/.opencode/agent/agent-permissions-handler.md +50 -0
  128. package/.opencode/agent/agent-simple-writer.md +45 -0
  129. package/.opencode/agent/agent-svetlana-code-review.md +39 -0
  130. package/.opencode/agent/agent-tanya-test-runner.md +57 -0
  131. package/.opencode/agent/agent-ui-designer.md +56 -0
  132. package/.opencode/agent/agent-viktor-sql-insights.md +34 -0
  133. package/.opencode/agent/agent-web-search.md +42 -0
  134. package/.opencode/agent/agent-yevgeni-discussions.md +37 -0
  135. package/.opencode/agent/agent-zara-zapier.md +53 -0
  136. package/.opencode/commands/app-squad.md +135 -0
  137. package/.opencode/commands/audit-squad.md +158 -0
  138. package/.opencode/commands/autoplan.md +563 -0
  139. package/.opencode/commands/cleanup-squad.md +98 -0
  140. package/.opencode/commands/config-squad.md +106 -0
  141. package/.opencode/commands/crud-squad.md +87 -0
  142. package/.opencode/commands/data-squad.md +97 -0
  143. package/.opencode/commands/debug-squad.md +303 -0
  144. package/.opencode/commands/doc-squad.md +65 -0
  145. package/.opencode/commands/handoff.md +137 -0
  146. package/.opencode/commands/health.md +49 -0
  147. package/.opencode/commands/help-agents.md +151 -0
  148. package/.opencode/commands/help-commands.md +32 -0
  149. package/.opencode/commands/help-faq.md +29 -0
  150. package/.opencode/commands/help-plugins.md +28 -0
  151. package/.opencode/commands/help-skills.md +7 -0
  152. package/.opencode/commands/help-tools.md +40 -0
  153. package/.opencode/commands/help.md +28 -0
  154. package/.opencode/commands/hotfix-squad.md +112 -0
  155. package/.opencode/commands/integration-squad.md +82 -0
  156. package/.opencode/commands/janitor-squad.md +167 -0
  157. package/.opencode/commands/learn-auto.md +120 -0
  158. package/.opencode/commands/learn.md +120 -0
  159. package/.opencode/commands/mcp-list.md +27 -0
  160. package/.opencode/commands/onboard-squad.md +140 -0
  161. package/.opencode/commands/plan-workspace.md +732 -0
  162. package/.opencode/commands/prd.md +131 -0
  163. package/.opencode/commands/project-status.md +82 -0
  164. package/.opencode/commands/publish.md +138 -0
  165. package/.opencode/commands/recap.md +69 -0
  166. package/.opencode/commands/restore.md +64 -0
  167. package/.opencode/commands/review-squad.md +152 -0
  168. package/.opencode/commands/save.md +24 -0
  169. package/.opencode/commands/stats.md +19 -0
  170. package/.opencode/commands/swarm.md +210 -0
  171. package/.opencode/commands/tool-builder.md +39 -0
  172. package/.opencode/commands/ws-pull.md +44 -0
  173. package/.opencode/opencode.json +21 -0
  174. package/package.json +1 -1
  175. package/scripts/postinstall.cjs +64 -0
  176. package/scripts/test-hal-tools.ts +154 -0
@@ -0,0 +1,542 @@
1
+ ---
2
+ name: SDK-function-fields
3
+ description: Complete guide to creating calculated function fields in Hailer - workflow, variable types, patterns
4
+ version: 1.2.0
5
+ triggers:
6
+ - function field
7
+ - calculated field
8
+ - formula field
9
+ - backlinks
10
+ - phase filtering
11
+ - Data metadata
12
+ ---
13
+
14
+ # Function Fields
15
+
16
+ Complete guide for creating calculated fields that auto-update when dependencies change.
17
+
18
+ ---
19
+
20
+ ## Overview
21
+
22
+ Function fields compute values from other fields. Examples:
23
+ - Total = Quantity × Unit Price
24
+ - Days Until Due = Due Date - Today
25
+ - Invoice Total = Sum of line item prices (filtered by phase)
26
+
27
+ ---
28
+
29
+ ## Step-by-Step Workflow
30
+
31
+ ### 1. Pull Latest
32
+ ```bash
33
+ npm run pull
34
+ ```
35
+
36
+ ### 2. Add Field Definition (fields.ts)
37
+
38
+ ```typescript
39
+ // workspace/[Workflow]_[id]/fields.ts
40
+ {
41
+ label: "Total Cost",
42
+ type: "number",
43
+ key: "total_cost",
44
+ function: "@function:totalCost",
45
+ functionEnabled: true,
46
+ editable: false,
47
+ functionVariables: {
48
+ quantity: {
49
+ type: "=",
50
+ data: [FieldIds.quantity]
51
+ },
52
+ unitPrice: {
53
+ type: "=",
54
+ data: [FieldIds.unit_price]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ### 3. Create Function File
61
+
62
+ Create `workspace/[Workflow]_[id]/functions/totalCost.ts`:
63
+
64
+ ```typescript
65
+ export function totalCost(dep: {
66
+ quantity: number | null;
67
+ unitPrice: number | null;
68
+ }): number {
69
+ const qty = Number(dep.quantity) || 0;
70
+ const price = Number(dep.unitPrice) || 0;
71
+ return qty * price;
72
+ }
73
+ ```
74
+
75
+ ### 4. Export Function
76
+
77
+ Edit `workspace/[Workflow]_[id]/functions/index.ts`:
78
+
79
+ ```typescript
80
+ export { totalCost } from './totalCost';
81
+ ```
82
+
83
+ ### 5. Test
84
+
85
+ ```typescript
86
+ // workspace/[Workflow]_[id]/main.test.ts
87
+ import { totalCost } from './functions';
88
+
89
+ describe('totalCost', () => {
90
+ it('multiplies quantity by unit price', () => {
91
+ expect(totalCost({ quantity: 5, unitPrice: 10 })).toBe(50);
92
+ });
93
+
94
+ it('handles null values', () => {
95
+ expect(totalCost({ quantity: null, unitPrice: 10 })).toBe(0);
96
+ });
97
+ });
98
+ ```
99
+
100
+ Run: `npm test`
101
+
102
+ ### 6. Push
103
+
104
+ ```bash
105
+ npm run fields-push:force
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Variable Types Reference
111
+
112
+ | Type | Name | data Array | Returns |
113
+ |------|------|------------|---------|
114
+ | `=` | Current field | `[FieldId]` | Single value |
115
+ | `>` | Forward link | `[LinkFieldId, TargetFieldId]` | Single value |
116
+ | `<` | Backlink | `[WorkflowId, TargetFieldId]` | **Array** |
117
+ | `?` | Static | `[WorkflowId, PhaseId]` | Phase ID string |
118
+
119
+ ---
120
+
121
+ ## Field Data Formats in Functions
122
+
123
+ What format does each field type return when used as a dependency variable?
124
+
125
+ | Field Type | Returns | Example |
126
+ |------------|---------|---------|
127
+ | `text`, `textarea` | `string` | `"Hello world"` |
128
+ | `numeric`, `numericunit` | `number` | `42.5` |
129
+ | `date`, `datetime` | `number` (ms timestamp) | `1730937600000` |
130
+ | `daterange`, `datetimerange` | `{ start: number, end: number }` | `{ start: 1730937600000, end: 1731024000000 }` |
131
+ | `time` | `number` (ms timestamp, includes date!) | `1765863000000` |
132
+ | `timerange` | `{ start: number, end: number }` | `{ start: 1765863000000, end: 1765915200000 }` (ms timestamps, includes date!) |
133
+ | `textpredefinedoptions` | `string` | `"High"` |
134
+ | `users`, `teams` | `string` (ID) | `"5f8a1b2c3d4e5f6a7b8c9d0e"` |
135
+ | `activitylink` | `string` (activity ID) | `"692abc123def456"` |
136
+ | `country` | `string` (ISO code) | `"FI"` |
137
+ | `numeric` + `modifier.checkbox` | `number` | `1` (true) or `0` (false) |
138
+
139
+ **Examples:**
140
+
141
+ ```javascript
142
+ // Date field (milliseconds timestamp)
143
+ const dueDate = dep.dueDate; // 1730937600000
144
+ const isOverdue = dueDate < Date.now();
145
+
146
+ // Daterange field (object with start/end)
147
+ const period = dep.eventPeriod; // { start: 1730937600000, end: 1731024000000 }
148
+ const startDate = period ? period.start : null;
149
+ const endDate = period ? period.end : null;
150
+ const durationMs = (endDate && startDate) ? (endDate - startDate) : 0;
151
+ const durationDays = Math.ceil(durationMs / 86400000);
152
+
153
+ // Time field (ms timestamp - includes date!)
154
+ const startTime = dep.startTime; // 1765863000000
155
+ // To extract just the time, use Date methods:
156
+ const date = new Date(startTime);
157
+ const hours = date.getUTCHours();
158
+ const minutes = date.getUTCMinutes();
159
+
160
+ // Timerange field (object with start/end as ms timestamps - includes date!)
161
+ const workHours = dep.workingHours; // { start: 1765863000000, end: 1765915200000 }
162
+ const durationMs = workHours ? (workHours.end - workHours.start) : 0;
163
+ const durationMinutes = durationMs / 60000;
164
+
165
+ // IMPORTANT: When to convert vs keep raw:
166
+ // - Display only (text output) → use Date methods to extract time
167
+ // - Write to another date/time field → keep as Unix milliseconds (no conversion!)
168
+
169
+ // Checkbox field (1 or 0)
170
+ const isActive = dep.isActive; // 1 or 0
171
+ if (isActive === 1) { /* checked */ }
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Backlink Arrays (`<`)
177
+
178
+ Backlinks return **arrays** because multiple activities can link to this one.
179
+
180
+ ```javascript
181
+ // functionVariables config
182
+ {
183
+ prices: {
184
+ type: "<",
185
+ data: [WorkflowIds.invoice_rows, InvoiceRows_FieldIds.total_price]
186
+ }
187
+ }
188
+
189
+ // In function - ALWAYS default to empty array
190
+ const prices = dep.prices || [];
191
+ let total = 0;
192
+ for (let i = 0; i < prices.length; i++) {
193
+ total += Number(prices[i]) || 0;
194
+ }
195
+ return total;
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Activity Metadata (`"meta"`)
201
+
202
+ The special `"meta"` value returns activity metadata. It works with **all variable types**:
203
+
204
+ ### Current Activity Metadata (`=`)
205
+
206
+ ```javascript
207
+ // Get metadata of THIS activity
208
+ {
209
+ myMeta: {
210
+ type: "=",
211
+ data: ["meta"]
212
+ }
213
+ }
214
+
215
+ // In function
216
+ const myPhase = dep.myMeta ? dep.myMeta.phase : null;
217
+ const myCreated = dep.myMeta ? dep.myMeta.created : 0;
218
+ ```
219
+
220
+ ### Forward Link Metadata (`>`)
221
+
222
+ ```javascript
223
+ // Get metadata of LINKED activity (this → other)
224
+ {
225
+ customerMeta: {
226
+ type: ">",
227
+ data: [FieldIds.customer_link, "meta"]
228
+ }
229
+ }
230
+
231
+ // In function
232
+ const customerPhase = dep.customerMeta ? dep.customerMeta.phaseName : 'Unknown';
233
+ ```
234
+
235
+ ### Backlink Metadata (`<`)
236
+
237
+ ```javascript
238
+ // Get metadata of activities linking TO this (others → this)
239
+ {
240
+ rowsMeta: {
241
+ type: "<",
242
+ data: [WorkflowIds.invoice_rows, "meta"]
243
+ }
244
+ }
245
+
246
+ // In function - returns ARRAY
247
+ const rows = dep.rowsMeta || [];
248
+ for (let i = 0; i < rows.length; i++) {
249
+ const phaseName = rows[i] ? rows[i].phaseName : 'Unknown';
250
+ }
251
+ ```
252
+
253
+ ### Metadata Structure
254
+
255
+ ```javascript
256
+ {
257
+ "_id": "694c9536acfa30f6df13201b", // Activity ID
258
+ "name": "Invoice row name", // Activity name
259
+ "process": "67dc1b7d3d2c9f6cf9a5468d", // Workflow ID
260
+ "phase": "67dc1b7d3d2c9f6cf9a546c4", // Phase ID
261
+ "processName": "Invoice Rows", // Workflow name
262
+ "phaseName": "Active", // Phase name
263
+ "created": 1766626614031, // Created timestamp (ms)
264
+ "updated": 1766626614031, // Updated timestamp (ms)
265
+ "completed": null, // Completed timestamp or null
266
+ "sequence": 1649, // Activity sequence number
267
+ "active": true // Is activity active
268
+ }
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Parallel Array Guarantee
274
+
275
+ **CRITICAL INSIGHT:** When you have multiple `<` (backlink) variables from the **SAME source workflow**, they are guaranteed to be:
276
+ - Same length
277
+ - Same order (index 0 in all arrays = same activity)
278
+
279
+ **This is a core Hailer feature that enables safe parallel iteration.**
280
+
281
+ ```javascript
282
+ const prices = dep['Total price'] || []; // [41.76, 26.1, 153.47]
283
+ const data = dep['Data'] || []; // [{...}, {...}, {...}]
284
+
285
+ // Index 0 in both = same Invoice Row activity
286
+ for (let i = 0; i < prices.length; i++) {
287
+ const price = Number(prices[i]) || 0;
288
+ const phaseName = data[i] ? data[i].phaseName : 'Unknown';
289
+ // price and phaseName are from the SAME activity
290
+ }
291
+ ```
292
+
293
+ **Arrays from DIFFERENT workflows** are independent.
294
+
295
+ ---
296
+
297
+ ## Static Variables (`?`)
298
+
299
+ Static variables provide phase IDs for comparison in your function logic.
300
+
301
+ ```javascript
302
+ // Get specific phase IDs to compare against
303
+ {
304
+ donePhase: {
305
+ type: "?",
306
+ data: [WorkflowIds.sales_activities, PhaseIds.done]
307
+ },
308
+ archivePhase: {
309
+ type: "?",
310
+ data: [WorkflowIds.sales_activities, PhaseIds.archive]
311
+ }
312
+ }
313
+
314
+ // In function - compare against metadata phase
315
+ const donePhase = dep.donePhase; // "627d14339381d6077ab90ce9"
316
+ const archivePhase = dep.archivePhase; // "639b49617a4413c20f15ac12"
317
+
318
+ if (item.phase === donePhase || item.phase === archivePhase) {
319
+ // Activity is in done or archive phase
320
+ }
321
+ ```
322
+
323
+ **Note:** To get `process` and `phase` IDs from activities, use `"meta"` (not `?`). Static `?` is only for providing known phase IDs for comparison.
324
+
325
+ ---
326
+
327
+ ## Common Patterns
328
+
329
+ ### Sum from Backlinks
330
+
331
+ ```javascript
332
+ function total(dep) {
333
+ const prices = dep.prices || [];
334
+ let sum = 0;
335
+ for (let i = 0; i < prices.length; i++) {
336
+ sum += Number(prices[i]) || 0;
337
+ }
338
+ return sum;
339
+ }
340
+ ```
341
+
342
+ ### Filter by Phase (Complete Example)
343
+
344
+ **functionVariables config:**
345
+ ```javascript
346
+ {
347
+ Data: {
348
+ type: "<",
349
+ data: [WorkflowIds.sales_activities, "meta"]
350
+ },
351
+ dl: {
352
+ type: "<",
353
+ data: [WorkflowIds.sales_activities, FieldIds.deadline]
354
+ },
355
+ done: {
356
+ type: "?",
357
+ data: [WorkflowIds.sales_activities, PhaseIds.done]
358
+ },
359
+ archive: {
360
+ type: "?",
361
+ data: [WorkflowIds.sales_activities, PhaseIds.archive]
362
+ }
363
+ }
364
+ ```
365
+
366
+ **Function (find latest deadline from done/archived activities):**
367
+ ```javascript
368
+ function latestCompletedDeadline(dep) {
369
+ const data = dep.Data || [];
370
+ const deadlines = dep.dl || [];
371
+ const donePhase = dep.done;
372
+ const archivePhase = dep.archive;
373
+
374
+ const arr = [];
375
+ for (let i = 0; i < data.length; i++) {
376
+ const item = data[i];
377
+ if ((item.phase === donePhase || item.phase === archivePhase) && deadlines[i]) {
378
+ arr.push(deadlines[i]);
379
+ }
380
+ }
381
+
382
+ if (arr.length === 0) return null;
383
+ return Math.max(...arr);
384
+ }
385
+ ```
386
+
387
+ ### Sum Active Only
388
+
389
+ ```javascript
390
+ function sumActiveOnly(dep) {
391
+ const prices = dep['Total price'] || [];
392
+ const data = dep['Data'] || [];
393
+ const activePhase = dep['Active'];
394
+
395
+ let total = 0;
396
+ for (let i = 0; i < prices.length; i++) {
397
+ if (data[i] && data[i].phase === activePhase) {
398
+ total += Number(prices[i]) || 0;
399
+ }
400
+ }
401
+ return total;
402
+ }
403
+ ```
404
+
405
+ ### Group by Phase
406
+
407
+ ```javascript
408
+ function groupByPhase(dep) {
409
+ const prices = dep['Total price'] || [];
410
+ const data = dep['Data'] || [];
411
+
412
+ const result = {};
413
+ for (let i = 0; i < prices.length; i++) {
414
+ const phaseName = data[i] ? data[i].phaseName : 'Unknown';
415
+ const price = Number(prices[i]) || 0;
416
+ result[phaseName] = (result[phaseName] || 0) + price;
417
+ }
418
+ return JSON.stringify(result);
419
+ }
420
+ ```
421
+
422
+ ### Exclude Multiple Phases
423
+
424
+ ```javascript
425
+ function sumExcludingCancelled(dep) {
426
+ const prices = dep['Total price'] || [];
427
+ const data = dep['Data'] || [];
428
+ const cancelledPhase = dep['Cancelled'];
429
+ const deletedPhase = dep['Deleted'];
430
+
431
+ let total = 0;
432
+ for (let i = 0; i < prices.length; i++) {
433
+ const phase = data[i] ? data[i].phase : null;
434
+ if (phase === cancelledPhase || phase === deletedPhase) continue;
435
+ total += Number(prices[i]) || 0;
436
+ }
437
+ return total;
438
+ }
439
+ ```
440
+
441
+ ### Conditional Text
442
+
443
+ ```javascript
444
+ function status(dep) {
445
+ if (!dep.dueDate) return "No due date";
446
+ if (dep.dueDate < Date.now()) return "Overdue";
447
+ return "On track";
448
+ }
449
+ ```
450
+
451
+ ### Forward Link Value
452
+
453
+ ```javascript
454
+ // Get value from linked activity
455
+ functionVariables: {
456
+ customerName: {
457
+ type: ">",
458
+ data: [FieldIds.customer_link, Customers_FieldIds.name]
459
+ }
460
+ }
461
+ ```
462
+
463
+ ### Emoji Based on Own Phase
464
+
465
+ ```javascript
466
+ // In name function: use activity's own phase, not parent workflow phase
467
+ functionVariables: {
468
+ myMeta: {
469
+ type: "=",
470
+ data: ["meta"] // Returns this activity's metadata
471
+ },
472
+ donePhase: {
473
+ type: "?",
474
+ data: [WorkflowIds.this_workflow, PhaseIds.done]
475
+ }
476
+ }
477
+
478
+ // In name function
479
+ function nameWithEmoji(dep) {
480
+ const myPhase = dep.myMeta ? dep.myMeta.phase : null;
481
+ const donePhase = dep.donePhase;
482
+
483
+ const prefix = (myPhase === donePhase) ? "✅ " : "🔄 ";
484
+ return prefix + dep.activityName;
485
+ }
486
+ ```
487
+
488
+ **Key:** Compare against the activity's OWN phase (from `"data"` metadata), not the parent workflow's phase.
489
+
490
+ ---
491
+
492
+ ## Code Style
493
+
494
+ **Use `const` and `let`** - NOT `var`:
495
+ ```javascript
496
+ // Correct
497
+ const prices = dep.prices || [];
498
+ let total = 0;
499
+
500
+ // Wrong
501
+ var prices = dep.prices || [];
502
+ ```
503
+
504
+ ---
505
+
506
+ ## Critical Rules
507
+
508
+ 1. **ES6 JavaScript only** - No TypeScript syntax in function body
509
+ 2. **Never return null/undefined** - Always return valid typed value
510
+ 3. **Handle null inputs** - Use `|| 0` or `|| ""` defaults
511
+ 4. **Same inputs = same outputs** - No randomness, deterministic
512
+ 5. **Helpers inside function** - Define helper functions in function body
513
+ 6. **Use enums** - Never hardcode field/workflow IDs
514
+ 7. **updateMany single-workflow rule** - `v3.activity.updateMany` targets one workflow per call. For cascading updates across multiple workflows, create separate function fields per target workflow. Don't mix activities from different workflows in one payload
515
+ 8. **Double-wrapped array for generic CRUD** - When outputting a payload for the monolith `POST /api/update-activities` handler, use `[[{_id, phaseId}, ...]]` (double-wrapped). The handler spreads parsed JSON as `updateMany(...args)`, so the first arg must be the array. Single-wrapped `[{_id, phaseId}]` causes "must be an array" validation error
516
+ 9. **Empty string validation** - Hailer rejects empty string `''` with "value is not allowed to be empty". Always return `'[]'` or a valid non-empty string as default from function fields
517
+ 10. **Data metadata shorthand** - `type: "="` with `data: ["data"]` returns this activity's metadata object (same as `data: ["meta"]`). Similarly, `type: "<"` with `data: [workflowId, "data"]` returns array of metadata objects for linked activities
518
+
519
+ ---
520
+
521
+ ## Common Mistakes
522
+
523
+ | Wrong | Right |
524
+ |-------|-------|
525
+ | `var arr = dep.arr` | `const arr = dep.arr \|\| []` |
526
+ | `arr[i] * 2` | `(Number(arr[i]) \|\| 0) * 2` |
527
+ | `data[i].phase` | `data[i] ? data[i].phase : null` |
528
+ | Assuming arrays match across workflows | Only same-workflow arrays are parallel |
529
+ | Hardcoding phase IDs | Use `?` static variables with enums |
530
+
531
+ ---
532
+
533
+ ## Checklist
534
+
535
+ Before creating a function with backlinks:
536
+
537
+ - [ ] All `<` arrays have `|| []` default
538
+ - [ ] Individual array values use `Number(x) || 0`
539
+ - [ ] Data metadata access has null check
540
+ - [ ] Phase comparisons use `?` static variables
541
+ - [ ] Using enums from workspace for all IDs
542
+ - [ ] Tested with Vitest before push
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: SDK-generate-skill
3
+ description: TypeScript type generation from Hailer workspace
4
+ version: 1.0.0
5
+ triggers: Generate types, TypeScript generation, update types, enums
6
+ ---
7
+
8
+ # SDK Type Generation
9
+
10
+ Generate TypeScript types from your Hailer workspace configuration.
11
+
12
+ ## Overview
13
+
14
+ The SDK generates TypeScript types and enums from your workspace configuration, providing type-safe access to:
15
+ - Workflow IDs
16
+ - Field IDs
17
+ - Phase IDs
18
+ - Team IDs
19
+ - Group IDs
20
+ - Member IDs
21
+
22
+ ---
23
+
24
+ ## Generated Files
25
+
26
+ After `npm run pull`, these files are auto-generated:
27
+
28
+ ```
29
+ workspace/
30
+ ├── enums.ts # Type-safe ID constants (DO NOT EDIT)
31
+ ├── hailer.d.ts # TypeScript definitions (DO NOT EDIT)
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Using Enums
37
+
38
+ ```typescript
39
+ import {
40
+ WorkflowIds,
41
+ Tasks_FieldIds,
42
+ Tasks_PhaseIds,
43
+ TeamIds,
44
+ WorkspaceMembers
45
+ } from './workspace/enums';
46
+
47
+ // Reference workflow
48
+ const workflowId = WorkflowIds.tasks;
49
+
50
+ // Reference field
51
+ const fieldId = Tasks_FieldIds.due_date;
52
+
53
+ // Reference phase
54
+ const phaseId = Tasks_PhaseIds.in_progress;
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Regenerating Types
60
+
61
+ Types are regenerated automatically when you run:
62
+
63
+ ```bash
64
+ npm run pull
65
+ ```
66
+
67
+ **When to regenerate:**
68
+ - After pushing new fields/phases/workflows
69
+ - After team/group changes
70
+ - When enums seem out of date
71
+
72
+ ---
73
+
74
+ ## Important Notes
75
+
76
+ 1. **Never edit enums.ts manually** - It's auto-generated
77
+ 2. **Pull after push** - Get new IDs into enums after adding items
78
+ 3. **Use enums everywhere** - Never hardcode IDs as strings
79
+ 4. **Type safety** - TypeScript will catch invalid ID references
80
+
81
+ ---
82
+
83
+ ## Troubleshooting
84
+
85
+ **Enum not found after adding field:**
86
+ 1. Ensure you ran `npm run fields-push:force`
87
+ 2. Run `npm run pull` to regenerate enums
88
+ 3. Check that the field was actually created in Hailer
89
+
90
+ **Type errors after changes:**
91
+ 1. Run `npm run pull` to sync
92
+ 2. Restart TypeScript server in your IDE