cloving 1.1.0 → 1.2.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.
Files changed (213) hide show
  1. package/README.md +121 -21
  2. package/assets/watch-fonts/fira-code-OFL.md +93 -0
  3. package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
  4. package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
  5. package/assets/watch-fonts/heebo-OFL.md +93 -0
  6. package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
  7. package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
  8. package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
  9. package/assets/watch-fonts/manifest.json +112 -0
  10. package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
  11. package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
  12. package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
  13. package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
  14. package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
  15. package/dist/src/authority/commands.d.ts +1 -0
  16. package/dist/src/authority/commands.js +25 -4
  17. package/dist/src/authority/dispatch-decision.d.ts +1 -0
  18. package/dist/src/authority/dispatch-decision.js +7 -4
  19. package/dist/src/authority/events.js +2 -2
  20. package/dist/src/authority/handoff-chain.d.ts +24 -0
  21. package/dist/src/authority/handoff-chain.js +46 -0
  22. package/dist/src/authority/reconciliation.d.ts +1 -0
  23. package/dist/src/authority/reconciliation.js +18 -11
  24. package/dist/src/authority/transitions.d.ts +1 -1
  25. package/dist/src/authority/transitions.js +3 -0
  26. package/dist/src/broker/actor/authority-queries.d.ts +4 -0
  27. package/dist/src/broker/actor/authority-queries.js +51 -2
  28. package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
  29. package/dist/src/broker/actor/effect-delivery.js +9 -0
  30. package/dist/src/broker/authority-admission.js +13 -5
  31. package/dist/src/chats/analysis-batches.d.ts +18 -0
  32. package/dist/src/chats/analysis-batches.js +15 -1
  33. package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
  34. package/dist/src/chats/analysis-checkpoint.js +31 -0
  35. package/dist/src/chats/analysis-input.d.ts +7 -1
  36. package/dist/src/chats/analysis-input.js +20 -6
  37. package/dist/src/chats/analysis-merge.d.ts +16 -0
  38. package/dist/src/chats/analysis-merge.js +134 -0
  39. package/dist/src/chats/analysis-prompt.d.ts +8 -0
  40. package/dist/src/chats/analysis-prompt.js +27 -1
  41. package/dist/src/chats/analysis-proposal.d.ts +2 -0
  42. package/dist/src/chats/analysis-proposal.js +9 -0
  43. package/dist/src/chats/analysis-shape.d.ts +1 -0
  44. package/dist/src/chats/analysis-shape.js +11 -4
  45. package/dist/src/chats/reference-mapping.d.ts +16 -0
  46. package/dist/src/chats/reference-mapping.js +153 -0
  47. package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
  48. package/dist/src/chats/workflow-generation-projection.js +75 -0
  49. package/dist/src/chats/workflow-generation-source.d.ts +25 -0
  50. package/dist/src/chats/workflow-generation-source.js +124 -0
  51. package/dist/src/chats/workflow-generation-text.d.ts +23 -0
  52. package/dist/src/chats/workflow-generation-text.js +74 -0
  53. package/dist/src/chats/workflow-generation.d.ts +69 -0
  54. package/dist/src/chats/workflow-generation.js +142 -0
  55. package/dist/src/cli/render.d.ts +1 -0
  56. package/dist/src/cli/render.js +60 -13
  57. package/dist/src/cli/usage.js +5 -2
  58. package/dist/src/cli/watch-board-view.d.ts +12 -1
  59. package/dist/src/cli/watch-board-view.js +57 -11
  60. package/dist/src/cli/watch-board.d.ts +7 -1
  61. package/dist/src/cli/watch-board.js +42 -10
  62. package/dist/src/cli/watch-client.d.ts +1 -1
  63. package/dist/src/cli/watch-client.js +7 -3
  64. package/dist/src/cli/watch-model.d.ts +10 -4
  65. package/dist/src/cli/watch-model.js +35 -58
  66. package/dist/src/cli/watch-page.js +21 -4
  67. package/dist/src/cli/watch-phases.js +3 -3
  68. package/dist/src/cli/watch-pr-command.d.ts +5 -2
  69. package/dist/src/cli/watch-pr-command.js +41 -4
  70. package/dist/src/cli/watch-pr-identity.d.ts +14 -2
  71. package/dist/src/cli/watch-pr-identity.js +25 -0
  72. package/dist/src/cli/watch-pr-observer.d.ts +1 -0
  73. package/dist/src/cli/watch-pr-observer.js +48 -6
  74. package/dist/src/cli/watch-pull-request.d.ts +10 -0
  75. package/dist/src/cli/watch-pull-request.js +130 -0
  76. package/dist/src/cli/watch-reads.d.ts +7 -1
  77. package/dist/src/cli/watch-reads.js +42 -20
  78. package/dist/src/cli/watch-render.d.ts +14 -1
  79. package/dist/src/cli/watch-render.js +2 -1
  80. package/dist/src/cli/watch-server.js +33 -2
  81. package/dist/src/cli/watch-turn.js +1 -1
  82. package/dist/src/cli/watch-view.d.ts +7 -0
  83. package/dist/src/cli/watch-view.js +51 -3
  84. package/dist/src/daemon/analysis-worker.d.ts +0 -2
  85. package/dist/src/daemon/analysis-worker.js +71 -19
  86. package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
  87. package/dist/src/daemon/chats-batch-binding.js +99 -0
  88. package/dist/src/daemon/chats-capture.d.ts +18 -0
  89. package/dist/src/daemon/chats-capture.js +194 -0
  90. package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
  91. package/dist/src/daemon/chats-checkpoints.js +186 -0
  92. package/dist/src/daemon/chats-files.d.ts +29 -2
  93. package/dist/src/daemon/chats-files.js +142 -3
  94. package/dist/src/daemon/chats-follow-up.d.ts +38 -0
  95. package/dist/src/daemon/chats-follow-up.js +180 -0
  96. package/dist/src/daemon/chats-question.d.ts +15 -0
  97. package/dist/src/daemon/chats-question.js +93 -0
  98. package/dist/src/daemon/chats-worker.d.ts +1 -0
  99. package/dist/src/daemon/chats-worker.js +52 -51
  100. package/dist/src/daemon/claude-turn.d.ts +2 -1
  101. package/dist/src/daemon/claude-turn.js +1 -1
  102. package/dist/src/daemon/claude-worker.d.ts +0 -2
  103. package/dist/src/daemon/claude-worker.js +6 -11
  104. package/dist/src/daemon/daemon.d.ts +5 -1
  105. package/dist/src/daemon/daemon.js +38 -10
  106. package/dist/src/daemon/delivery-router.js +6 -0
  107. package/dist/src/daemon/github-calls.d.ts +4 -0
  108. package/dist/src/daemon/github-calls.js +16 -7
  109. package/dist/src/daemon/github-ci.d.ts +6 -0
  110. package/dist/src/daemon/github-ci.js +170 -0
  111. package/dist/src/daemon/github-merge-call.d.ts +14 -0
  112. package/dist/src/daemon/github-merge-call.js +73 -0
  113. package/dist/src/daemon/github-merge.d.ts +3 -5
  114. package/dist/src/daemon/github-merge.js +52 -95
  115. package/dist/src/daemon/github-methods.d.ts +28 -0
  116. package/dist/src/daemon/github-methods.js +80 -16
  117. package/dist/src/daemon/github-operation.d.ts +2 -0
  118. package/dist/src/daemon/github-retry.d.ts +6 -0
  119. package/dist/src/daemon/github-retry.js +49 -0
  120. package/dist/src/daemon/github-ticket-status.js +27 -1
  121. package/dist/src/daemon/github-worker.js +2 -1
  122. package/dist/src/daemon/handoff-worker.d.ts +10 -0
  123. package/dist/src/daemon/handoff-worker.js +232 -0
  124. package/dist/src/daemon/issue-queue.d.ts +1 -0
  125. package/dist/src/daemon/issue-queue.js +94 -42
  126. package/dist/src/daemon/notify-journal.d.ts +18 -0
  127. package/dist/src/daemon/notify-journal.js +108 -0
  128. package/dist/src/daemon/notify-message.js +2 -1
  129. package/dist/src/daemon/notify.js +176 -37
  130. package/dist/src/daemon/retrying-turn.d.ts +3 -0
  131. package/dist/src/daemon/retrying-turn.js +55 -9
  132. package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
  133. package/dist/src/daemon/ticket-dependency-waits.js +60 -0
  134. package/dist/src/daemon/ticket-registration.d.ts +8 -4
  135. package/dist/src/daemon/ticket-registration.js +76 -22
  136. package/dist/src/daemon/ticket-source.d.ts +4 -1
  137. package/dist/src/daemon/ticket-source.js +8 -2
  138. package/dist/src/decision-visibility.d.ts +2 -0
  139. package/dist/src/decision-visibility.js +10 -0
  140. package/dist/src/execution-pin.d.ts +4 -0
  141. package/dist/src/execution-pin.js +23 -2
  142. package/dist/src/host/start-delivery.d.ts +2 -1
  143. package/dist/src/host/start-delivery.js +1 -1
  144. package/dist/src/host/step-bridge-host.d.ts +1 -0
  145. package/dist/src/host/step-bridge-host.js +16 -0
  146. package/dist/src/host/world-reconciliation.d.ts +1 -0
  147. package/dist/src/host/world-reconciliation.js +3 -0
  148. package/dist/src/host/world-runs.js +5 -0
  149. package/dist/src/issue-identity.d.ts +6 -0
  150. package/dist/src/issue-identity.js +23 -0
  151. package/dist/src/service/queue-runs.d.ts +3 -0
  152. package/dist/src/service/queue-runs.js +39 -0
  153. package/dist/src/service/registry.d.ts +8 -2
  154. package/dist/src/service/registry.js +20 -8
  155. package/dist/src/service/replies.d.ts +2 -0
  156. package/dist/src/service/run-handlers.js +44 -12
  157. package/dist/src/service/service.d.ts +3 -0
  158. package/dist/src/service/service.js +3 -1
  159. package/dist/src/service/submission-handler.d.ts +1 -0
  160. package/dist/src/service/submission-handler.js +92 -13
  161. package/dist/src/service/worker-totals.js +25 -1
  162. package/dist/src/step-bridge.d.ts +9 -0
  163. package/dist/src/steps/handoff.d.ts +2 -0
  164. package/dist/src/steps/handoff.js +13 -0
  165. package/dist/src/ticket-dependencies.d.ts +1 -0
  166. package/dist/src/ticket-dependencies.js +15 -0
  167. package/dist/src/ticket-status.js +7 -4
  168. package/dist/src/token-prices.d.ts +2 -0
  169. package/dist/src/token-prices.js +41 -9
  170. package/dist/src/worker-usage.d.ts +9 -0
  171. package/dist/src/worker-usage.js +1 -1
  172. package/dist/src/workflow-admission.d.ts +1 -0
  173. package/dist/src/workflow-candidate.js +1 -0
  174. package/dist/src/workflow-contracts.d.ts +2 -0
  175. package/dist/src/workflow-contracts.js +8 -3
  176. package/dist/src/workflow-declaration.d.ts +11 -0
  177. package/dist/src/workflow-declaration.js +71 -15
  178. package/dist/src/workflow-discovery.d.ts +18 -0
  179. package/dist/src/workflow-discovery.js +33 -0
  180. package/dist/src/workflow-refusals.d.ts +6 -0
  181. package/dist/src/workflow-refusals.js +39 -0
  182. package/dist/src/workflows.d.ts +2 -1
  183. package/dist/src/workflows.js +1 -0
  184. package/dist/tools/admit-workflow.d.ts +13 -0
  185. package/dist/tools/admit-workflow.js +94 -0
  186. package/dist/tools/self-host-project.d.ts +17 -0
  187. package/dist/tools/self-host-project.js +177 -0
  188. package/dist/tools/self-host.js +27 -81
  189. package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
  190. package/dist/tools/workflow-bundle-dependencies.js +93 -0
  191. package/dist/tools/workflow-bundles.d.ts +20 -0
  192. package/dist/tools/workflow-bundles.js +173 -0
  193. package/dist/tools/workflow-candidate-counts.d.ts +8 -0
  194. package/dist/tools/workflow-candidate-counts.js +251 -0
  195. package/dist/tools/workflow-candidate-program.d.ts +11 -0
  196. package/dist/tools/workflow-candidate-program.js +78 -0
  197. package/dist/tools/workflow-candidate-source.d.ts +6 -0
  198. package/dist/tools/workflow-candidate-source.js +107 -0
  199. package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
  200. package/dist/tools/workflow-candidate-syntax.js +219 -0
  201. package/dist/tools/workflow-folder-capture.d.ts +17 -0
  202. package/dist/tools/workflow-folder-capture.js +216 -0
  203. package/dist/tools/workflow-folders.d.ts +33 -0
  204. package/dist/tools/workflow-folders.js +150 -0
  205. package/dist/tools/workflow-source-effects.d.ts +10 -0
  206. package/dist/tools/workflow-source-effects.js +213 -0
  207. package/dist/workflows/analyze-chats.d.ts +6 -1
  208. package/dist/workflows/analyze-chats.declaration.json +14 -1
  209. package/dist/workflows/analyze-chats.js +188 -11
  210. package/dist/workflows/develop-prd.js +71 -3
  211. package/dist/workflows/develop-ticket.js +5 -0
  212. package/dist/workflows/ticket-prompts.js +12 -2
  213. package/package.json +21 -3
@@ -0,0 +1,251 @@
1
+ import ts from "typescript";
2
+ import { protectedCall, handoffCall, propertyName, } from "./workflow-candidate-syntax.js";
3
+ import { referenceSymbol } from "./workflow-source-effects.js";
4
+ const zero = { knownCallCount: 0, knownUnitTotal: 0 };
5
+ function add(first, second) {
6
+ return {
7
+ knownCallCount: Math.min(Number.MAX_SAFE_INTEGER, first.knownCallCount + second.knownCallCount),
8
+ knownUnitTotal: Math.min(Number.MAX_SAFE_INTEGER, first.knownUnitTotal + second.knownUnitTotal),
9
+ };
10
+ }
11
+ function maximum(first, second) {
12
+ return {
13
+ knownCallCount: Math.max(first.knownCallCount, second.knownCallCount),
14
+ knownUnitTotal: Math.max(first.knownUnitTotal, second.knownUnitTotal),
15
+ };
16
+ }
17
+ function multiply(total, repetitions) {
18
+ return {
19
+ knownCallCount: Math.min(Number.MAX_SAFE_INTEGER, total.knownCallCount * repetitions),
20
+ knownUnitTotal: Math.min(Number.MAX_SAFE_INTEGER, total.knownUnitTotal * repetitions),
21
+ };
22
+ }
23
+ function units(node, symbols) {
24
+ if (handoffCall(node, symbols))
25
+ return 1;
26
+ const argument = node.arguments[0];
27
+ if (!argument || !ts.isObjectLiteralExpression(argument))
28
+ return 0;
29
+ const property = argument.properties.find((entry) => entry.name && propertyName(entry.name) === "units");
30
+ if (!property || !ts.isPropertyAssignment(property))
31
+ return 0;
32
+ const value = constantNumber(property.initializer);
33
+ return value !== undefined && Number.isSafeInteger(value) && value > 0
34
+ ? value
35
+ : 0;
36
+ }
37
+ function loopIncrement(node, variable) {
38
+ if ((ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node)) &&
39
+ ts.isIdentifier(node.operand) &&
40
+ node.operand.text === variable)
41
+ return unaryIncrement(node.operator);
42
+ return assignmentIncrement(node, variable);
43
+ }
44
+ function unaryIncrement(operator) {
45
+ if (operator === ts.SyntaxKind.PlusPlusToken)
46
+ return 1;
47
+ if (operator === ts.SyntaxKind.MinusMinusToken)
48
+ return -1;
49
+ return undefined;
50
+ }
51
+ function assignmentIncrement(node, variable) {
52
+ if (!ts.isBinaryExpression(node) ||
53
+ !ts.isIdentifier(node.left) ||
54
+ node.left.text !== variable)
55
+ return undefined;
56
+ const amount = constantNumber(node.right);
57
+ if (amount === undefined)
58
+ return undefined;
59
+ if (node.operatorToken.kind === ts.SyntaxKind.PlusEqualsToken)
60
+ return amount;
61
+ if (node.operatorToken.kind === ts.SyntaxKind.MinusEqualsToken)
62
+ return -amount;
63
+ return undefined;
64
+ }
65
+ function constantNumber(node) {
66
+ if (ts.isParenthesizedExpression(node))
67
+ return constantNumber(node.expression);
68
+ if (ts.isNumericLiteral(node)) {
69
+ const value = Number(node.text);
70
+ return Number.isFinite(value) ? value : undefined;
71
+ }
72
+ if (!ts.isPrefixUnaryExpression(node))
73
+ return undefined;
74
+ const value = constantNumber(node.operand);
75
+ if (value === undefined)
76
+ return undefined;
77
+ if (node.operator === ts.SyntaxKind.PlusToken)
78
+ return value;
79
+ if (node.operator === ts.SyntaxKind.MinusToken)
80
+ return -value;
81
+ return undefined;
82
+ }
83
+ function rangeIterations(input) {
84
+ const { start, end, increment, operator } = input;
85
+ if (![
86
+ ts.SyntaxKind.LessThanToken,
87
+ ts.SyntaxKind.LessThanEqualsToken,
88
+ ts.SyntaxKind.GreaterThanToken,
89
+ ts.SyntaxKind.GreaterThanEqualsToken,
90
+ ].includes(operator))
91
+ return undefined;
92
+ let iterations = 0;
93
+ let value = start;
94
+ while (iterations < 101 && loopContinues(value, { end, operator })) {
95
+ iterations += 1;
96
+ value += increment;
97
+ }
98
+ return iterations;
99
+ }
100
+ function loopContinues(value, condition) {
101
+ const { end, operator } = condition;
102
+ if (operator === ts.SyntaxKind.LessThanToken)
103
+ return value < end;
104
+ if (operator === ts.SyntaxKind.LessThanEqualsToken)
105
+ return value <= end;
106
+ if (operator === ts.SyntaxKind.GreaterThanToken)
107
+ return value > end;
108
+ return value >= end;
109
+ }
110
+ function referencesCounter(node, context) {
111
+ if (!context.counter)
112
+ return false;
113
+ if (ts.isIdentifier(node) &&
114
+ referenceSymbol(node, { parent: node.parent, checker: context.checker }) ===
115
+ context.counter)
116
+ return true;
117
+ return (ts.forEachChild(node, (child) => referencesCounter(child, context) ? true : undefined) ?? false);
118
+ }
119
+ function assignmentTarget(node) {
120
+ if (ts.isBinaryExpression(node) &&
121
+ node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
122
+ node.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
123
+ return node.left;
124
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
125
+ unaryIncrement(node.operator) !== undefined)
126
+ return node.operand;
127
+ return undefined;
128
+ }
129
+ function writesCounter(node, context) {
130
+ if (ts.isVariableDeclaration(node) && node.initializer !== undefined)
131
+ return referencesCounter(node.name, context);
132
+ if (ts.isForOfStatement(node) || ts.isForInStatement(node))
133
+ return referencesCounter(node.initializer, context);
134
+ const target = assignmentTarget(node);
135
+ return target !== undefined && referencesCounter(target, context);
136
+ }
137
+ function uncertainLoopBody(node, context) {
138
+ if (ts.isBreakStatement(node) ||
139
+ ts.isContinueStatement(node) ||
140
+ ts.isReturnStatement(node) ||
141
+ ts.isThrowStatement(node) ||
142
+ writesCounter(node, context))
143
+ return true;
144
+ return (ts.forEachChild(node, (child) => uncertainLoopBody(child, context) ? true : undefined) ?? false);
145
+ }
146
+ function loopIterations(node, symbols) {
147
+ const { initializer, condition, incrementor } = node;
148
+ const declaration = loopStart(initializer);
149
+ if (!declaration)
150
+ return undefined;
151
+ if (!condition || !ts.isBinaryExpression(condition))
152
+ return undefined;
153
+ if (!ts.isIdentifier(condition.left) ||
154
+ condition.left.text !== declaration.variable.text)
155
+ return undefined;
156
+ if (!incrementor)
157
+ return undefined;
158
+ const end = constantNumber(condition.right);
159
+ if (end === undefined)
160
+ return undefined;
161
+ const increment = loopIncrement(incrementor, declaration.variable.text);
162
+ if (increment === undefined)
163
+ return undefined;
164
+ if (uncertainLoopBody(node.statement, {
165
+ checker: symbols.checker,
166
+ counter: symbols.checker.getSymbolAtLocation(declaration.variable),
167
+ }))
168
+ return undefined;
169
+ return rangeIterations({
170
+ start: declaration.start,
171
+ end,
172
+ increment,
173
+ operator: condition.operatorToken.kind,
174
+ });
175
+ }
176
+ function loopStart(initializer) {
177
+ if (!initializer ||
178
+ !ts.isVariableDeclarationList(initializer) ||
179
+ initializer.declarations.length !== 1)
180
+ return undefined;
181
+ const declaration = initializer.declarations[0];
182
+ if (!declaration ||
183
+ !ts.isIdentifier(declaration.name) ||
184
+ !declaration.initializer)
185
+ return undefined;
186
+ const start = constantNumber(declaration.initializer);
187
+ if (start === undefined)
188
+ return undefined;
189
+ return {
190
+ variable: declaration.name,
191
+ start,
192
+ };
193
+ }
194
+ export function candidateEffectTotals(source, symbols) {
195
+ function count(node) {
196
+ if (!node)
197
+ return zero;
198
+ if (ts.isIfStatement(node))
199
+ return add(count(node.expression), maximum(count(node.thenStatement), count(node.elseStatement)));
200
+ if (ts.isConditionalExpression(node))
201
+ return add(count(node.condition), maximum(count(node.whenTrue), count(node.whenFalse)));
202
+ if (ts.isForStatement(node)) {
203
+ const iterations = loopIterations(node, symbols) ?? 1;
204
+ const body = count(node.statement);
205
+ const headers = add(count(node.initializer), add(count(node.condition), count(node.incrementor)));
206
+ return add(headers, multiply(body, iterations));
207
+ }
208
+ if (ts.isForOfStatement(node) &&
209
+ ts.isArrayLiteralExpression(node.expression))
210
+ return add(count(node.expression), multiply(add(count(node.initializer), count(node.statement)), arrayIterations(node, symbols)));
211
+ if (ts.isSwitchStatement(node))
212
+ return add(count(node.expression), switchTotals(node.caseBlock, count));
213
+ let total = protectedCall(node, symbols)
214
+ ? {
215
+ knownCallCount: 1,
216
+ knownUnitTotal: units(node, symbols),
217
+ }
218
+ : zero;
219
+ ts.forEachChild(node, (child) => {
220
+ total = add(total, count(child));
221
+ });
222
+ return total;
223
+ }
224
+ return count(source);
225
+ }
226
+ function arrayIterations(node, symbols) {
227
+ if (!ts.isArrayLiteralExpression(node.expression))
228
+ return 1;
229
+ return uncertainLoopBody(node.statement, {
230
+ checker: symbols.checker,
231
+ counter: undefined,
232
+ })
233
+ ? 1
234
+ : node.expression.elements.length;
235
+ }
236
+ function switchTotals(block, count) {
237
+ let suffix = zero;
238
+ let largest = zero;
239
+ let conditions = zero;
240
+ for (const clause of [...block.clauses].reverse()) {
241
+ if (ts.isCaseClause(clause))
242
+ conditions = add(conditions, count(clause.expression));
243
+ const ends = clause.statements.some((node) => ts.isBreakStatement(node) ||
244
+ ts.isReturnStatement(node) ||
245
+ ts.isThrowStatement(node));
246
+ const statements = clause.statements.reduce((total, statement) => add(total, count(statement)), zero);
247
+ suffix = add(statements, ends ? zero : suffix);
248
+ largest = maximum(largest, suffix);
249
+ }
250
+ return add(conditions, largest);
251
+ }
@@ -0,0 +1,11 @@
1
+ import ts from "typescript";
2
+ export declare const candidatePackageRoot: string;
3
+ export interface CandidatePackageOptions {
4
+ readonly packageRoot?: string;
5
+ }
6
+ export declare function resolvedSymbol(checker: ts.TypeChecker, symbol: ts.Symbol | undefined): ts.Symbol | undefined;
7
+ export declare function candidateProgram(text: string, installed?: CandidatePackageOptions): {
8
+ readonly program: ts.Program;
9
+ readonly source: ts.SourceFile;
10
+ readonly modules: ReadonlyMap<string, ts.SourceFile>;
11
+ };
@@ -0,0 +1,78 @@
1
+ import { dirname, join, resolve, sep } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import ts from "typescript";
4
+ import metadata from "#package" with { type: "json" };
5
+ export const candidatePackageRoot = dirname(fileURLToPath(import.meta.resolve("#package")));
6
+ export function resolvedSymbol(checker, symbol) {
7
+ if (!symbol || (symbol.flags & ts.SymbolFlags.Alias) === 0)
8
+ return symbol;
9
+ return checker.getAliasedSymbol(symbol);
10
+ }
11
+ export function candidateProgram(text, installed = {}) {
12
+ const packageRoot = resolve(installed.packageRoot ?? candidatePackageRoot);
13
+ const candidate = join(packageRoot, "candidate.ts");
14
+ const entries = new Map(Object.entries(metadata.exports).map(([name, entry]) => [
15
+ `cloving${name.slice(1)}`,
16
+ resolve(packageRoot, entry.types),
17
+ ]));
18
+ const options = {
19
+ target: ts.ScriptTarget.ES2024,
20
+ module: ts.ModuleKind.ESNext,
21
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
22
+ lib: ["lib.es2024.d.ts", "lib.dom.d.ts"],
23
+ types: [],
24
+ strict: true,
25
+ noEmit: true,
26
+ };
27
+ const host = trustedHost({ candidate, text, options, entries, packageRoot });
28
+ const program = ts.createProgram([candidate, ...entries.values()], options, host);
29
+ const source = program.getSourceFile(candidate);
30
+ if (!source)
31
+ throw new Error("candidate-analysis-unavailable");
32
+ const modules = new Map();
33
+ for (const [name, file] of entries) {
34
+ const declaration = program.getSourceFile(file);
35
+ if (!declaration)
36
+ throw new Error("candidate-surface-unavailable");
37
+ modules.set(name, declaration);
38
+ }
39
+ return { program, source, modules };
40
+ }
41
+ function trustedHost(input) {
42
+ const { candidate, text, options, entries, packageRoot } = input;
43
+ const libraries = dirname(ts.getDefaultLibFilePath(options)) + sep;
44
+ const declarations = join(packageRoot, "dist/src") + sep;
45
+ const readFile = (file) => {
46
+ if (file === candidate)
47
+ return text;
48
+ const absolute = resolve(file);
49
+ if (!absolute.endsWith(".d.ts"))
50
+ return undefined;
51
+ if (!absolute.startsWith(libraries) && !absolute.startsWith(declarations))
52
+ return undefined;
53
+ return ts.sys.readFile(absolute);
54
+ };
55
+ const host = ts.createCompilerHost(options);
56
+ host.readFile = readFile;
57
+ host.fileExists = (file) => readFile(file) !== undefined;
58
+ host.getSourceFile = (file, languageVersion) => {
59
+ const content = readFile(file);
60
+ return content === undefined
61
+ ? undefined
62
+ : ts.createSourceFile(file, content, languageVersion, true);
63
+ };
64
+ host.resolveModuleNameLiterals = (names, containingFile) => names.map(({ text: name }) => {
65
+ const installed = entries.get(name);
66
+ if (installed)
67
+ return {
68
+ resolvedModule: {
69
+ resolvedFileName: installed,
70
+ extension: ts.Extension.Dts,
71
+ },
72
+ };
73
+ if (containingFile === candidate)
74
+ return { resolvedModule: undefined };
75
+ return ts.resolveModuleName(name, containingFile, options, host);
76
+ });
77
+ return host;
78
+ }
@@ -0,0 +1,6 @@
1
+ import { type CandidatePackageOptions } from "./workflow-candidate-program.ts";
2
+ import type { CandidateAnalysis, InstalledWorkflowExports } from "../src/workflow-candidate.ts";
3
+ export declare function analyzeWorkflowCandidate(text: string, installed?: CandidatePackageOptions): {
4
+ readonly analysis: CandidateAnalysis;
5
+ readonly installedExports: InstalledWorkflowExports;
6
+ };
@@ -0,0 +1,107 @@
1
+ import ts from "typescript";
2
+ import { candidateProgram, resolvedSymbol, } from "./workflow-candidate-program.js";
3
+ import { protectedEffectFact, passiveCandidateModule, unsupportedCandidateNode, workflowShape, } from "./workflow-candidate-syntax.js";
4
+ import { sourceEffectNodes } from "./workflow-source-effects.js";
5
+ import { candidateEffectTotals } from "./workflow-candidate-counts.js";
6
+ function exportSnapshot(modules, checker) {
7
+ return Object.fromEntries([...modules].map(([name, declaration]) => {
8
+ const symbol = checker.getSymbolAtLocation(declaration);
9
+ return [
10
+ name,
11
+ symbol
12
+ ? checker
13
+ .getExportsOfModule(symbol)
14
+ .map((entry) => entry.name)
15
+ .sort()
16
+ : [],
17
+ ];
18
+ }));
19
+ }
20
+ function protectedHelper(modules, options) {
21
+ const { checker } = options;
22
+ const protectedModule = modules.get(options.subpath);
23
+ const moduleSymbol = protectedModule && checker.getSymbolAtLocation(protectedModule);
24
+ return (moduleSymbol &&
25
+ resolvedSymbol(checker, checker
26
+ .getExportsOfModule(moduleSymbol)
27
+ .find((entry) => entry.name === options.name)));
28
+ }
29
+ function sourceFacts(symbols) {
30
+ const { source } = symbols;
31
+ const imports = [];
32
+ const effects = [];
33
+ const unsupportedForms = [];
34
+ function visit(node) {
35
+ const imported = sourceImport(node);
36
+ if (imported)
37
+ imports.push(imported);
38
+ const effect = protectedEffectFact(node, symbols);
39
+ if (effect)
40
+ effects.push(effect);
41
+ if (unsupportedCandidateNode(node, symbols))
42
+ unsupportedForms.push("unsupported-syntax");
43
+ ts.forEachChild(node, visit);
44
+ }
45
+ visit(source);
46
+ return {
47
+ imports,
48
+ effects,
49
+ directEffects: sourceEffectNodes(source, {
50
+ checker: symbols.checker,
51
+ profile: "candidate",
52
+ })
53
+ .filter((node) => !ts.isIdentifier(node) || node.text !== "require")
54
+ .map(() => "restricted-effect"),
55
+ unsupportedForms,
56
+ ...candidateEffectTotals(source, symbols),
57
+ };
58
+ }
59
+ function sourceImport(node) {
60
+ if (!ts.isImportDeclaration(node) ||
61
+ !ts.isStringLiteral(node.moduleSpecifier))
62
+ return undefined;
63
+ const bindings = node.importClause?.namedBindings;
64
+ return {
65
+ module: node.moduleSpecifier.text,
66
+ names: bindings && ts.isNamedImports(bindings)
67
+ ? bindings.elements.map((entry) => entry.propertyName?.text ?? entry.name.text)
68
+ : [],
69
+ };
70
+ }
71
+ export function analyzeWorkflowCandidate(text, installed = {}) {
72
+ const { program, source, modules } = candidateProgram(text, installed);
73
+ const checker = program.getTypeChecker();
74
+ const symbols = {
75
+ checker,
76
+ source,
77
+ protectedSymbol: protectedHelper(modules, {
78
+ checker,
79
+ subpath: "cloving/workflows/steps/protected-effect",
80
+ name: "protectedEffect",
81
+ }),
82
+ handoffSymbol: protectedHelper(modules, {
83
+ checker,
84
+ subpath: "cloving/workflows/steps/handoff",
85
+ name: "handoff",
86
+ }),
87
+ };
88
+ const facts = sourceFacts(symbols);
89
+ const invalid = !workflowShape(source) ||
90
+ source.referencedFiles.length > 0 ||
91
+ source.typeReferenceDirectives.length > 0 ||
92
+ source.libReferenceDirectives.length > 0 ||
93
+ program.getSyntacticDiagnostics(source).length > 0;
94
+ return {
95
+ analysis: {
96
+ ...facts,
97
+ unsupportedForms: [
98
+ ...facts.unsupportedForms,
99
+ ...(invalid ? ["workflow-shape"] : []),
100
+ ...(facts.directEffects.length === 0 && !passiveCandidateModule(source)
101
+ ? ["module-initialization"]
102
+ : []),
103
+ ],
104
+ },
105
+ installedExports: exportSnapshot(modules, checker),
106
+ };
107
+ }
@@ -0,0 +1,15 @@
1
+ import ts from "typescript";
2
+ import type { CandidateEffect } from "../src/workflow-candidate.ts";
3
+ export interface CandidateSymbols {
4
+ readonly checker: ts.TypeChecker;
5
+ readonly source: ts.SourceFile;
6
+ readonly protectedSymbol: ts.Symbol | undefined;
7
+ readonly handoffSymbol: ts.Symbol | undefined;
8
+ }
9
+ export declare function propertyName(name: ts.PropertyName): string | undefined;
10
+ export declare function protectedCall(node: ts.Node, symbols: CandidateSymbols): node is ts.CallExpression;
11
+ export declare function handoffCall(node: ts.CallExpression, symbols: CandidateSymbols): boolean;
12
+ export declare function protectedEffectFact(node: ts.Node, symbols: CandidateSymbols): CandidateEffect | undefined;
13
+ export declare function unsupportedCandidateNode(node: ts.Node, symbols: CandidateSymbols): boolean;
14
+ export declare function workflowShape(source: ts.SourceFile): boolean;
15
+ export declare function passiveCandidateModule(source: ts.SourceFile): boolean;
@@ -0,0 +1,219 @@
1
+ import ts from "typescript";
2
+ import { resolvedSymbol } from "./workflow-candidate-program.js";
3
+ import { referenceSymbol } from "./workflow-source-effects.js";
4
+ export function propertyName(name) {
5
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name))
6
+ return name.text;
7
+ return undefined;
8
+ }
9
+ export function protectedCall(node, symbols) {
10
+ if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression))
11
+ return false;
12
+ const symbol = resolvedSymbol(symbols.checker, symbols.checker.getSymbolAtLocation(node.expression));
13
+ return (symbol !== undefined &&
14
+ [symbols.protectedSymbol, symbols.handoffSymbol].includes(symbol));
15
+ }
16
+ export function handoffCall(node, symbols) {
17
+ return (symbols.handoffSymbol !== undefined &&
18
+ resolvedSymbol(symbols.checker, symbols.checker.getSymbolAtLocation(node.expression)) === symbols.handoffSymbol);
19
+ }
20
+ function literalField(node, field) {
21
+ const property = node.properties.find((entry) => entry.name && propertyName(entry.name) === field);
22
+ if (!property || !ts.isPropertyAssignment(property))
23
+ return null;
24
+ return ts.isStringLiteral(property.initializer)
25
+ ? property.initializer.text
26
+ : null;
27
+ }
28
+ export function protectedEffectFact(node, symbols) {
29
+ if (!protectedCall(node, symbols))
30
+ return undefined;
31
+ if (handoffCall(node, symbols))
32
+ return undefined;
33
+ const argument = node.arguments[0];
34
+ if (!argument || !ts.isObjectLiteralExpression(argument))
35
+ return undefined;
36
+ return {
37
+ capability: literalField(argument, "capability"),
38
+ method: literalField(argument, "method"),
39
+ };
40
+ }
41
+ function escapedHelper(node, symbols) {
42
+ if (!ts.isIdentifier(node))
43
+ return false;
44
+ const symbol = resolvedSymbol(symbols.checker, referenceSymbol(node, { parent: node.parent, checker: symbols.checker }));
45
+ if (symbol === undefined ||
46
+ ![symbols.protectedSymbol, symbols.handoffSymbol].includes(symbol))
47
+ return false;
48
+ if (ts.isImportSpecifier(node.parent))
49
+ return false;
50
+ return (!ts.isCallExpression(node.parent) ||
51
+ node.parent.expression !== node ||
52
+ node.parent.questionDotToken !== undefined);
53
+ }
54
+ function invalidImport(node) {
55
+ if (!ts.isImportDeclaration(node))
56
+ return false;
57
+ const clause = node.importClause;
58
+ return (!clause ||
59
+ clause.name !== undefined ||
60
+ !clause.namedBindings ||
61
+ !ts.isNamedImports(clause.namedBindings) ||
62
+ node.attributes !== undefined);
63
+ }
64
+ function invalidRequest(node, symbols) {
65
+ if (!protectedCall(node, symbols))
66
+ return false;
67
+ if (!workflowBodyCall(node))
68
+ return true;
69
+ const argument = node.arguments[0];
70
+ if (node.arguments.length !== 1 ||
71
+ !argument ||
72
+ !ts.isObjectLiteralExpression(argument))
73
+ return true;
74
+ const names = argument.properties.map((entry) => entry.name && propertyName(entry.name));
75
+ return (names.includes(undefined) ||
76
+ new Set(names).size !== names.length ||
77
+ argument.properties.some((entry) => !ts.isPropertyAssignment(entry)));
78
+ }
79
+ function workflowBodyCall(node) {
80
+ let child = node;
81
+ let parent = node.parent;
82
+ while (!ts.isFunctionLike(parent)) {
83
+ if (ts.isSourceFile(parent) || ts.isClassLike(parent))
84
+ return false;
85
+ child = parent;
86
+ parent = parent.parent;
87
+ }
88
+ if (!ts.isFunctionDeclaration(parent) || !ts.isSourceFile(parent.parent))
89
+ return false;
90
+ return (parent.body === child &&
91
+ (ts.getCombinedModifierFlags(parent) & ts.ModifierFlags.Export) !== 0);
92
+ }
93
+ function dynamicLoading(node, symbols) {
94
+ if (node.kind === ts.SyntaxKind.ImportKeyword ||
95
+ ts.isImportEqualsDeclaration(node) ||
96
+ ts.isImportTypeNode(node) ||
97
+ ts.isMetaProperty(node))
98
+ return true;
99
+ if (!ts.isIdentifier(node) || node.text !== "require")
100
+ return false;
101
+ const symbol = symbols.checker.getSymbolAtLocation(node);
102
+ return !symbol?.declarations?.some((entry) => entry.getSourceFile() === symbols.source);
103
+ }
104
+ function ambientDeclaration(node) {
105
+ if (ts.isModuleDeclaration(node))
106
+ return true;
107
+ return (ts.canHaveModifiers(node) &&
108
+ (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword));
109
+ }
110
+ function computedAccess(node) {
111
+ if (ts.isComputedPropertyName(node))
112
+ return true;
113
+ return (ts.isElementAccessExpression(node) &&
114
+ !ts.isStringLiteral(node.argumentExpression) &&
115
+ !ts.isNumericLiteral(node.argumentExpression));
116
+ }
117
+ export function unsupportedCandidateNode(node, symbols) {
118
+ const checks = [
119
+ escapedHelper(node, symbols),
120
+ invalidImport(node),
121
+ invalidRequest(node, symbols),
122
+ dynamicLoading(node, symbols),
123
+ ambientDeclaration(node),
124
+ computedAccess(node),
125
+ ts.isExportDeclaration(node) || ts.isExportAssignment(node),
126
+ ts.isSpreadAssignment(node) || ts.isSpreadElement(node),
127
+ node.kind === ts.SyntaxKind.ThisKeyword || ts.isWithStatement(node),
128
+ ts.isStringLiteral(node) && node.text === "use step",
129
+ ];
130
+ return checks.some(Boolean);
131
+ }
132
+ export function workflowShape(source) {
133
+ const exported = source.statements.filter((node) => ts.isFunctionDeclaration(node) &&
134
+ (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword));
135
+ const candidate = exported[0];
136
+ if (exported.length !== 1 ||
137
+ !candidate ||
138
+ !ts.isFunctionDeclaration(candidate))
139
+ return false;
140
+ if (!workflowFunction(candidate))
141
+ return false;
142
+ const directives = [];
143
+ function visit(node) {
144
+ if (ts.isStringLiteral(node) && node.text === "use workflow")
145
+ directives.push(node);
146
+ ts.forEachChild(node, visit);
147
+ }
148
+ visit(source);
149
+ return directives.length === 1;
150
+ }
151
+ function workflowFunction(candidate) {
152
+ if (!candidate.name || !candidate.body || candidate.asteriskToken)
153
+ return false;
154
+ const modifiers = ts.getModifiers(candidate) ?? [];
155
+ if (!modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) ||
156
+ modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword))
157
+ return false;
158
+ const first = candidate.body.statements[0];
159
+ return first !== undefined && workflowDirective(first);
160
+ }
161
+ function workflowDirective(node) {
162
+ return (ts.isExpressionStatement(node) &&
163
+ ts.isStringLiteral(node.expression) &&
164
+ node.expression.text === "use workflow");
165
+ }
166
+ function unwrappedInitializer(node) {
167
+ if (ts.isParenthesizedExpression(node) ||
168
+ ts.isAsExpression(node) ||
169
+ ts.isSatisfiesExpression(node) ||
170
+ ts.isTypeAssertionExpression(node))
171
+ return node.expression;
172
+ return undefined;
173
+ }
174
+ function passiveCompound(node) {
175
+ if (ts.isArrayLiteralExpression(node))
176
+ return node.elements.every((element) => ts.isOmittedExpression(element) || passiveInitializer(element));
177
+ if (ts.isObjectLiteralExpression(node))
178
+ return node.properties.every((property) => ts.isPropertyAssignment(property) &&
179
+ !ts.isComputedPropertyName(property.name) &&
180
+ passiveInitializer(property.initializer));
181
+ return false;
182
+ }
183
+ function passiveInitializer(node) {
184
+ if (!node)
185
+ return false;
186
+ if (ts.isLiteralExpression(node))
187
+ return true;
188
+ if ([
189
+ ts.SyntaxKind.TrueKeyword,
190
+ ts.SyntaxKind.FalseKeyword,
191
+ ts.SyntaxKind.NullKeyword,
192
+ ].includes(node.kind))
193
+ return true;
194
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node))
195
+ return true;
196
+ if (ts.isPrefixUnaryExpression(node))
197
+ return ([ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken].includes(node.operator) && ts.isNumericLiteral(node.operand));
198
+ const inner = unwrappedInitializer(node);
199
+ return inner ? passiveInitializer(inner) : passiveCompound(node);
200
+ }
201
+ function passiveStatement(node) {
202
+ if (ts.isVariableStatement(node))
203
+ return ((node.declarationList.flags & ts.NodeFlags.Const) !== 0 &&
204
+ node.declarationList.declarations.every((declaration) => (ts.getCombinedModifierFlags(declaration) &
205
+ ts.ModifierFlags.Export) ===
206
+ 0 &&
207
+ ts.isIdentifier(declaration.name) &&
208
+ passiveInitializer(declaration.initializer)));
209
+ if (ts.isExpressionStatement(node))
210
+ return ts.isStringLiteral(node.expression);
211
+ return (ts.isImportDeclaration(node) ||
212
+ ts.isTypeAliasDeclaration(node) ||
213
+ ts.isInterfaceDeclaration(node) ||
214
+ ts.isFunctionDeclaration(node) ||
215
+ ts.isEmptyStatement(node));
216
+ }
217
+ export function passiveCandidateModule(source) {
218
+ return source.statements.every(passiveStatement);
219
+ }