cloving 1.0.1 → 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 (309) hide show
  1. package/README.md +251 -20
  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 +33 -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 +3 -0
  23. package/dist/src/authority/reconciliation.js +21 -5
  24. package/dist/src/authority/transitions.d.ts +1 -1
  25. package/dist/src/authority/transitions.js +5 -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/authority-session.d.ts +2 -0
  29. package/dist/src/broker/actor/authority-session.js +3 -4
  30. package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
  31. package/dist/src/broker/actor/effect-delivery.js +9 -0
  32. package/dist/src/broker/authority-admission.js +13 -5
  33. package/dist/src/chats/analysis-batches.d.ts +42 -0
  34. package/dist/src/chats/analysis-batches.js +97 -0
  35. package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
  36. package/dist/src/chats/analysis-checkpoint.js +31 -0
  37. package/dist/src/chats/analysis-contract.d.ts +80 -0
  38. package/dist/src/chats/analysis-contract.js +16 -0
  39. package/dist/src/chats/analysis-data.d.ts +3 -0
  40. package/dist/src/chats/analysis-data.js +85 -0
  41. package/dist/src/chats/analysis-input.d.ts +18 -0
  42. package/dist/src/chats/analysis-input.js +97 -0
  43. package/dist/src/chats/analysis-links.d.ts +2 -0
  44. package/dist/src/chats/analysis-links.js +25 -0
  45. package/dist/src/chats/analysis-merge.d.ts +16 -0
  46. package/dist/src/chats/analysis-merge.js +134 -0
  47. package/dist/src/chats/analysis-prompt.d.ts +18 -0
  48. package/dist/src/chats/analysis-prompt.js +42 -0
  49. package/dist/src/chats/analysis-proposal.d.ts +4 -0
  50. package/dist/src/chats/analysis-proposal.js +41 -0
  51. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  52. package/dist/src/chats/analysis-provenance.js +99 -0
  53. package/dist/src/chats/analysis-shape.d.ts +4 -0
  54. package/dist/src/chats/analysis-shape.js +235 -0
  55. package/dist/src/chats/analysis.d.ts +3 -0
  56. package/dist/src/chats/analysis.js +42 -0
  57. package/dist/src/chats/corpus-input.d.ts +5 -0
  58. package/dist/src/chats/corpus-input.js +288 -0
  59. package/dist/src/chats/destination.js +3 -1
  60. package/dist/src/chats/import.d.ts +8 -6
  61. package/dist/src/chats/import.js +195 -23
  62. package/dist/src/chats/input-file.d.ts +3 -0
  63. package/dist/src/chats/input-file.js +32 -0
  64. package/dist/src/chats/normalize.d.ts +11 -2
  65. package/dist/src/chats/normalize.js +18 -2
  66. package/dist/src/chats/previous-import.d.ts +5 -0
  67. package/dist/src/chats/previous-import.js +34 -0
  68. package/dist/src/chats/reader.d.ts +7 -1
  69. package/dist/src/chats/reader.js +46 -15
  70. package/dist/src/chats/redact-import.d.ts +14 -0
  71. package/dist/src/chats/redact-import.js +141 -0
  72. package/dist/src/chats/redaction.d.ts +41 -0
  73. package/dist/src/chats/redaction.js +161 -0
  74. package/dist/src/chats/reference-mapping.d.ts +16 -0
  75. package/dist/src/chats/reference-mapping.js +153 -0
  76. package/dist/src/chats/revision-input.d.ts +3 -0
  77. package/dist/src/chats/revision-input.js +180 -0
  78. package/dist/src/chats/revision.d.ts +48 -0
  79. package/dist/src/chats/revision.js +94 -0
  80. package/dist/src/chats/selection.d.ts +25 -0
  81. package/dist/src/chats/selection.js +109 -0
  82. package/dist/src/chats/source.d.ts +23 -0
  83. package/dist/src/chats/source.js +135 -0
  84. package/dist/src/chats/structure.d.ts +57 -0
  85. package/dist/src/chats/structure.js +216 -0
  86. package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
  87. package/dist/src/chats/workflow-generation-projection.js +75 -0
  88. package/dist/src/chats/workflow-generation-source.d.ts +25 -0
  89. package/dist/src/chats/workflow-generation-source.js +124 -0
  90. package/dist/src/chats/workflow-generation-text.d.ts +23 -0
  91. package/dist/src/chats/workflow-generation-text.js +74 -0
  92. package/dist/src/chats/workflow-generation.d.ts +69 -0
  93. package/dist/src/chats/workflow-generation.js +142 -0
  94. package/dist/src/cli/arguments.d.ts +8 -0
  95. package/dist/src/cli/arguments.js +94 -13
  96. package/dist/src/cli/client.d.ts +1 -0
  97. package/dist/src/cli/client.js +38 -1
  98. package/dist/src/cli/commands.d.ts +3 -0
  99. package/dist/src/cli/commands.js +29 -11
  100. package/dist/src/cli/main.d.ts +2 -1
  101. package/dist/src/cli/main.js +61 -14
  102. package/dist/src/cli/render.d.ts +3 -0
  103. package/dist/src/cli/render.js +122 -7
  104. package/dist/src/cli/usage-render.d.ts +3 -0
  105. package/dist/src/cli/usage-render.js +28 -0
  106. package/dist/src/cli/usage.d.ts +3 -2
  107. package/dist/src/cli/usage.js +42 -7
  108. package/dist/src/cli/watch-actions.d.ts +10 -0
  109. package/dist/src/cli/watch-actions.js +58 -0
  110. package/dist/src/cli/watch-board-view.d.ts +44 -1
  111. package/dist/src/cli/watch-board-view.js +137 -18
  112. package/dist/src/cli/watch-board.d.ts +7 -1
  113. package/dist/src/cli/watch-board.js +48 -11
  114. package/dist/src/cli/watch-card-client.d.ts +1 -0
  115. package/dist/src/cli/watch-card-client.js +44 -0
  116. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  117. package/dist/src/cli/watch-card-forms.js +33 -0
  118. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  119. package/dist/src/cli/watch-card-reply.js +34 -0
  120. package/dist/src/cli/watch-card-view.d.ts +40 -0
  121. package/dist/src/cli/watch-card-view.js +86 -0
  122. package/dist/src/cli/watch-client.d.ts +1 -0
  123. package/dist/src/cli/watch-client.js +122 -0
  124. package/dist/src/cli/watch-model.d.ts +10 -4
  125. package/dist/src/cli/watch-model.js +61 -67
  126. package/dist/src/cli/watch-page.d.ts +1 -1
  127. package/dist/src/cli/watch-page.js +89 -86
  128. package/dist/src/cli/watch-phases.js +3 -3
  129. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  130. package/dist/src/cli/watch-pr-board.js +26 -0
  131. package/dist/src/cli/watch-pr-command.d.ts +18 -0
  132. package/dist/src/cli/watch-pr-command.js +74 -0
  133. package/dist/src/cli/watch-pr-identity.d.ts +32 -0
  134. package/dist/src/cli/watch-pr-identity.js +87 -0
  135. package/dist/src/cli/watch-pr-observer.d.ts +20 -0
  136. package/dist/src/cli/watch-pr-observer.js +150 -0
  137. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  138. package/dist/src/cli/watch-pr-settings.js +47 -0
  139. package/dist/src/cli/watch-pull-request.d.ts +10 -0
  140. package/dist/src/cli/watch-pull-request.js +130 -0
  141. package/dist/src/cli/watch-reads.d.ts +7 -1
  142. package/dist/src/cli/watch-reads.js +44 -18
  143. package/dist/src/cli/watch-render.d.ts +29 -2
  144. package/dist/src/cli/watch-render.js +11 -3
  145. package/dist/src/cli/watch-run-input.d.ts +14 -0
  146. package/dist/src/cli/watch-run-input.js +49 -0
  147. package/dist/src/cli/watch-server.d.ts +3 -0
  148. package/dist/src/cli/watch-server.js +77 -8
  149. package/dist/src/cli/watch-start-view.d.ts +18 -0
  150. package/dist/src/cli/watch-start-view.js +77 -0
  151. package/dist/src/cli/watch-turn.d.ts +1 -0
  152. package/dist/src/cli/watch-turn.js +8 -3
  153. package/dist/src/cli/watch-usage.d.ts +6 -0
  154. package/dist/src/cli/watch-usage.js +65 -0
  155. package/dist/src/cli/watch-view.d.ts +22 -0
  156. package/dist/src/cli/watch-view.js +111 -19
  157. package/dist/src/cli.js +48 -6
  158. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  159. package/dist/src/daemon/analysis-launch.js +81 -0
  160. package/dist/src/daemon/analysis-token.d.ts +6 -0
  161. package/dist/src/daemon/analysis-token.js +88 -0
  162. package/dist/src/daemon/analysis-worker.d.ts +20 -0
  163. package/dist/src/daemon/analysis-worker.js +271 -0
  164. package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
  165. package/dist/src/daemon/chats-batch-binding.js +99 -0
  166. package/dist/src/daemon/chats-capture.d.ts +18 -0
  167. package/dist/src/daemon/chats-capture.js +194 -0
  168. package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
  169. package/dist/src/daemon/chats-checkpoints.js +186 -0
  170. package/dist/src/daemon/chats-files.d.ts +42 -0
  171. package/dist/src/daemon/chats-files.js +252 -0
  172. package/dist/src/daemon/chats-follow-up.d.ts +38 -0
  173. package/dist/src/daemon/chats-follow-up.js +180 -0
  174. package/dist/src/daemon/chats-question.d.ts +15 -0
  175. package/dist/src/daemon/chats-question.js +93 -0
  176. package/dist/src/daemon/chats-worker.d.ts +13 -0
  177. package/dist/src/daemon/chats-worker.js +201 -0
  178. package/dist/src/daemon/claude-turn.d.ts +11 -0
  179. package/dist/src/daemon/claude-turn.js +96 -15
  180. package/dist/src/daemon/claude-worker.d.ts +2 -1
  181. package/dist/src/daemon/claude-worker.js +92 -45
  182. package/dist/src/daemon/codex-stream.js +16 -0
  183. package/dist/src/daemon/daemon.d.ts +8 -1
  184. package/dist/src/daemon/daemon.js +51 -14
  185. package/dist/src/daemon/delivery-router.js +6 -0
  186. package/dist/src/daemon/github-calls.d.ts +4 -0
  187. package/dist/src/daemon/github-calls.js +16 -7
  188. package/dist/src/daemon/github-ci.d.ts +6 -0
  189. package/dist/src/daemon/github-ci.js +170 -0
  190. package/dist/src/daemon/github-merge-call.d.ts +14 -0
  191. package/dist/src/daemon/github-merge-call.js +73 -0
  192. package/dist/src/daemon/github-merge.d.ts +3 -5
  193. package/dist/src/daemon/github-merge.js +52 -95
  194. package/dist/src/daemon/github-methods.d.ts +28 -0
  195. package/dist/src/daemon/github-methods.js +84 -16
  196. package/dist/src/daemon/github-operation.d.ts +2 -0
  197. package/dist/src/daemon/github-retry.d.ts +6 -0
  198. package/dist/src/daemon/github-retry.js +49 -0
  199. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  200. package/dist/src/daemon/github-ticket-status.js +294 -0
  201. package/dist/src/daemon/github-worker.js +2 -1
  202. package/dist/src/daemon/handoff-worker.d.ts +10 -0
  203. package/dist/src/daemon/handoff-worker.js +232 -0
  204. package/dist/src/daemon/issue-queue.d.ts +2 -0
  205. package/dist/src/daemon/issue-queue.js +186 -43
  206. package/dist/src/daemon/notify-journal.d.ts +18 -0
  207. package/dist/src/daemon/notify-journal.js +108 -0
  208. package/dist/src/daemon/notify-message.js +2 -1
  209. package/dist/src/daemon/notify.js +176 -37
  210. package/dist/src/daemon/operation-journal.d.ts +1 -0
  211. package/dist/src/daemon/operation-journal.js +4 -2
  212. package/dist/src/daemon/retrying-turn.d.ts +12 -0
  213. package/dist/src/daemon/retrying-turn.js +231 -0
  214. package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
  215. package/dist/src/daemon/ticket-dependency-waits.js +60 -0
  216. package/dist/src/daemon/ticket-registration.d.ts +8 -4
  217. package/dist/src/daemon/ticket-registration.js +76 -22
  218. package/dist/src/daemon/ticket-source.d.ts +4 -1
  219. package/dist/src/daemon/ticket-source.js +8 -2
  220. package/dist/src/decision-visibility.d.ts +2 -0
  221. package/dist/src/decision-visibility.js +10 -0
  222. package/dist/src/execution-pin.d.ts +4 -0
  223. package/dist/src/execution-pin.js +23 -2
  224. package/dist/src/host/start-delivery.d.ts +2 -1
  225. package/dist/src/host/start-delivery.js +1 -1
  226. package/dist/src/host/step-bridge-host.d.ts +1 -0
  227. package/dist/src/host/step-bridge-host.js +16 -0
  228. package/dist/src/host/world-reconciliation.d.ts +3 -0
  229. package/dist/src/host/world-reconciliation.js +9 -0
  230. package/dist/src/host/world-runs.js +14 -1
  231. package/dist/src/issue-identity.d.ts +6 -0
  232. package/dist/src/issue-identity.js +23 -0
  233. package/dist/src/service/queue-runs.d.ts +3 -0
  234. package/dist/src/service/queue-runs.js +39 -0
  235. package/dist/src/service/registry.d.ts +8 -2
  236. package/dist/src/service/registry.js +20 -8
  237. package/dist/src/service/replies.d.ts +2 -0
  238. package/dist/src/service/run-handlers.js +54 -30
  239. package/dist/src/service/service.d.ts +3 -0
  240. package/dist/src/service/service.js +3 -1
  241. package/dist/src/service/submission-handler.d.ts +1 -0
  242. package/dist/src/service/submission-handler.js +92 -13
  243. package/dist/src/service/worker-totals.d.ts +3 -0
  244. package/dist/src/service/worker-totals.js +52 -0
  245. package/dist/src/step-bridge.d.ts +9 -0
  246. package/dist/src/steps/handoff.d.ts +2 -0
  247. package/dist/src/steps/handoff.js +13 -0
  248. package/dist/src/ticket-dependencies.d.ts +1 -0
  249. package/dist/src/ticket-dependencies.js +15 -0
  250. package/dist/src/ticket-status.d.ts +8 -0
  251. package/dist/src/ticket-status.js +48 -0
  252. package/dist/src/token-prices.d.ts +21 -0
  253. package/dist/src/token-prices.js +108 -0
  254. package/dist/src/worker-usage.d.ts +31 -0
  255. package/dist/src/worker-usage.js +44 -0
  256. package/dist/src/workflow-admission.d.ts +1 -0
  257. package/dist/src/workflow-candidate.d.ts +29 -0
  258. package/dist/src/workflow-candidate.js +60 -0
  259. package/dist/src/workflow-contracts.d.ts +4 -0
  260. package/dist/src/workflow-contracts.js +32 -5
  261. package/dist/src/workflow-declaration.d.ts +12 -1
  262. package/dist/src/workflow-declaration.js +71 -13
  263. package/dist/src/workflow-discovery.d.ts +18 -0
  264. package/dist/src/workflow-discovery.js +33 -0
  265. package/dist/src/workflow-refusals.d.ts +6 -0
  266. package/dist/src/workflow-refusals.js +39 -0
  267. package/dist/src/workflows.d.ts +3 -1
  268. package/dist/src/workflows.js +2 -0
  269. package/dist/tools/admit-workflow.d.ts +13 -0
  270. package/dist/tools/admit-workflow.js +94 -0
  271. package/dist/tools/self-host-processes.js +7 -2
  272. package/dist/tools/self-host-project.d.ts +17 -0
  273. package/dist/tools/self-host-project.js +177 -0
  274. package/dist/tools/self-host-workflows.d.ts +1 -1
  275. package/dist/tools/self-host-workflows.js +2 -2
  276. package/dist/tools/self-host.js +30 -84
  277. package/dist/tools/self-update-host.d.ts +7 -0
  278. package/dist/tools/self-update-host.js +48 -1
  279. package/dist/tools/self-update.d.ts +1 -0
  280. package/dist/tools/self-update.js +85 -13
  281. package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
  282. package/dist/tools/workflow-bundle-dependencies.js +93 -0
  283. package/dist/tools/workflow-bundles.d.ts +20 -0
  284. package/dist/tools/workflow-bundles.js +173 -0
  285. package/dist/tools/workflow-candidate-counts.d.ts +8 -0
  286. package/dist/tools/workflow-candidate-counts.js +251 -0
  287. package/dist/tools/workflow-candidate-program.d.ts +11 -0
  288. package/dist/tools/workflow-candidate-program.js +78 -0
  289. package/dist/tools/workflow-candidate-source.d.ts +6 -0
  290. package/dist/tools/workflow-candidate-source.js +107 -0
  291. package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
  292. package/dist/tools/workflow-candidate-syntax.js +219 -0
  293. package/dist/tools/workflow-folder-capture.d.ts +17 -0
  294. package/dist/tools/workflow-folder-capture.js +216 -0
  295. package/dist/tools/workflow-folders.d.ts +33 -0
  296. package/dist/tools/workflow-folders.js +150 -0
  297. package/dist/tools/workflow-source-effects.d.ts +10 -0
  298. package/dist/tools/workflow-source-effects.js +213 -0
  299. package/dist/workflows/analyze-chats.d.ts +15 -0
  300. package/dist/workflows/analyze-chats.declaration.json +22 -0
  301. package/dist/workflows/analyze-chats.js +250 -0
  302. package/dist/workflows/develop-prd.js +71 -3
  303. package/dist/workflows/develop-ticket.d.ts +1 -7
  304. package/dist/workflows/develop-ticket.js +47 -141
  305. package/dist/workflows/issue-labels.d.ts +3 -1
  306. package/dist/workflows/issue-labels.js +34 -0
  307. package/dist/workflows/ticket-prompts.d.ts +35 -0
  308. package/dist/workflows/ticket-prompts.js +143 -0
  309. package/package.json +25 -5
@@ -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
+ }
@@ -0,0 +1,17 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export type WorkflowCaptureCode = "unsafe-workflow-path" | "workflow-read-failed" | "workflow-source-changed" | "unsupported-source" | "invalid-declaration";
3
+ export interface WorkflowFolder {
4
+ readonly path: string;
5
+ readonly components: readonly {
6
+ readonly path: string;
7
+ readonly stats: BigIntStats;
8
+ }[];
9
+ }
10
+ export declare function inspectWorkflowFolder(root: string, parts: readonly string[]): Promise<WorkflowFolder>;
11
+ export declare function captureWorkflowCandidate(folder: WorkflowFolder, name: string): Promise<{
12
+ source: string | undefined;
13
+ declaration: string | undefined;
14
+ codes: WorkflowCaptureCode[];
15
+ }>;
16
+ export declare function workflowCaptureFailure(error: unknown): WorkflowCaptureCode;
17
+ export declare function workflowFolderEntries(folder: WorkflowFolder): Promise<string[]>;
@@ -0,0 +1,216 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, readdir, realpath, } from "node:fs/promises";
3
+ import { isAbsolute, join, relative, sep } from "node:path";
4
+ function contained(root, path) {
5
+ const suffix = relative(root, path);
6
+ return (!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`));
7
+ }
8
+ function safeComponent(name) {
9
+ return (name.length > 0 &&
10
+ name !== "." &&
11
+ name !== ".." &&
12
+ !/[/\\:]/.test(name) &&
13
+ !name.startsWith("~"));
14
+ }
15
+ export async function inspectWorkflowFolder(root, parts) {
16
+ const canonical = await realpath(root);
17
+ if (parts.some((part) => !safeComponent(part)))
18
+ throw new Error("unsafe-workflow-path");
19
+ const components = [];
20
+ let path = canonical;
21
+ for (const part of ["", ...parts]) {
22
+ path = join(path, part);
23
+ const stats = await lstat(path, { bigint: true }).catch(async (error) => {
24
+ await verifyFolder({ path, components });
25
+ throw error;
26
+ });
27
+ if (stats.isSymbolicLink())
28
+ throw new Error("unsafe-workflow-path");
29
+ if (!stats.isDirectory())
30
+ throw new Error("workflow-read-failed");
31
+ components.push({ path, stats });
32
+ }
33
+ const resolved = await realpath(path).catch(changedPathError);
34
+ const folder = { path, components };
35
+ await verifyFolder(folder);
36
+ if (!contained(canonical, resolved))
37
+ throw new Error("unsafe-workflow-path");
38
+ return folder;
39
+ }
40
+ function sameIdentity(left, right) {
41
+ return (left.dev === right.dev && left.ino === right.ino && left.mode === right.mode);
42
+ }
43
+ function sameSnapshot(left, right) {
44
+ return (sameIdentity(left, right) &&
45
+ left.size === right.size &&
46
+ left.mtimeNs === right.mtimeNs &&
47
+ left.ctimeNs === right.ctimeNs);
48
+ }
49
+ async function verifyFolder(folder) {
50
+ for (const component of folder.components) {
51
+ const compare = component.path === folder.path ? sameSnapshot : sameIdentity;
52
+ if (!compare(component.stats, await capturedStats(component.path)))
53
+ throw new Error("workflow-source-changed");
54
+ }
55
+ }
56
+ function capturedStats(path) {
57
+ return lstat(path, { bigint: true }).catch(changedPathError);
58
+ }
59
+ function changedPathError(error) {
60
+ if (error instanceof Error &&
61
+ "code" in error &&
62
+ (error.code === "ENOENT" ||
63
+ error.code === "ENOTDIR" ||
64
+ error.code === "ELOOP"))
65
+ throw new Error("workflow-source-changed");
66
+ throw error;
67
+ }
68
+ async function openSource(folder, name) {
69
+ const flags = constants.O_RDONLY | constants.O_NONBLOCK;
70
+ if (process.platform === "darwin")
71
+ return open(join(folder.path, name), flags | 0x20000000);
72
+ if (process.platform !== "linux")
73
+ throw new Error("workflow-read-failed");
74
+ const handles = [];
75
+ try {
76
+ let path = folder.components[0]?.path;
77
+ if (!path)
78
+ throw new Error("workflow-read-failed");
79
+ for (const component of folder.components) {
80
+ const handle = await open(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
81
+ handles.push(handle);
82
+ if (!sameIdentity(component.stats, await handle.stat({ bigint: true })))
83
+ throw new Error("workflow-source-changed");
84
+ const next = folder.components[handles.length];
85
+ path = `/proc/self/fd/${String(handle.fd)}/${next ? relative(component.path, next.path) : name}`;
86
+ }
87
+ return await open(path, flags | constants.O_NOFOLLOW);
88
+ }
89
+ finally {
90
+ for (const handle of handles.reverse())
91
+ await handle.close();
92
+ }
93
+ }
94
+ async function inspectFile(folder, name) {
95
+ const path = join(folder.path, name);
96
+ const stats = await lstat(path, { bigint: true });
97
+ if (!stats.isFile())
98
+ throw new Error("unsafe-workflow-path");
99
+ const resolved = await realpath(path).catch(changedPathError);
100
+ if (!sameSnapshot(stats, await capturedStats(path)))
101
+ throw new Error("workflow-source-changed");
102
+ if (!contained(folder.path, resolved))
103
+ throw new Error("unsafe-workflow-path");
104
+ return { path, name, stats };
105
+ }
106
+ async function captureFile(folder, file) {
107
+ const { stats } = file;
108
+ const handle = await openSource(folder, file.name).catch(changedPathError);
109
+ try {
110
+ if (!sameSnapshot(stats, await handle.stat({ bigint: true })))
111
+ throw new Error("workflow-source-changed");
112
+ const bytes = await handle.readFile();
113
+ if (!sameSnapshot(stats, await handle.stat({ bigint: true })) ||
114
+ BigInt(bytes.length) !== stats.size)
115
+ throw new Error("workflow-source-changed");
116
+ return { ...file, bytes };
117
+ }
118
+ finally {
119
+ await handle.close();
120
+ }
121
+ }
122
+ async function captureFiles(folder, names) {
123
+ const inspected = [];
124
+ const captured = [];
125
+ const codes = [];
126
+ for (const name of names) {
127
+ try {
128
+ inspected.push(await inspectFile(folder, name));
129
+ }
130
+ catch (error) {
131
+ codes.push(workflowCaptureFailure(error));
132
+ }
133
+ }
134
+ for (const file of inspected) {
135
+ try {
136
+ captured.push(await captureFile(folder, file));
137
+ }
138
+ catch (error) {
139
+ codes.push(workflowCaptureFailure(error));
140
+ }
141
+ }
142
+ for (const file of captured) {
143
+ if (!sameSnapshot(file.stats, await capturedStats(file.path)))
144
+ throw new Error("workflow-source-changed");
145
+ }
146
+ return { captured, codes };
147
+ }
148
+ function decodedFile(input, codes) {
149
+ if (input.bytes === undefined)
150
+ return undefined;
151
+ const text = input.bytes.toString("utf8");
152
+ if (input.bytes.equals(Buffer.from(text)))
153
+ return text;
154
+ codes.push(input.code);
155
+ return undefined;
156
+ }
157
+ export async function captureWorkflowCandidate(folder, name) {
158
+ if (!safeComponent(name) || !name.endsWith(".ts") || name.endsWith(".d.ts"))
159
+ throw new Error("unsafe-workflow-path");
160
+ await verifyFolder(folder);
161
+ const declarationName = name.slice(0, -3) + ".declaration.json";
162
+ const { captured, codes } = await captureFiles(folder, [
163
+ name,
164
+ declarationName,
165
+ ]);
166
+ await verifyFolder(folder);
167
+ const source = decodedFile({
168
+ bytes: captured.find((file) => file.name === name)?.bytes,
169
+ code: "unsupported-source",
170
+ }, codes);
171
+ const declaration = decodedFile({
172
+ bytes: captured.find((file) => file.name === declarationName)?.bytes,
173
+ code: "invalid-declaration",
174
+ }, codes);
175
+ return {
176
+ source,
177
+ declaration,
178
+ codes: [
179
+ ...new Set(codes.includes("unsafe-workflow-path")
180
+ ? codes.filter((code) => code !== "workflow-read-failed")
181
+ : codes),
182
+ ].sort(),
183
+ };
184
+ }
185
+ export function workflowCaptureFailure(error) {
186
+ const code = error instanceof Error && "code" in error ? error.code : undefined;
187
+ if (code === "ELOOP")
188
+ return "unsafe-workflow-path";
189
+ const message = error instanceof Error ? error.message : "";
190
+ if (message === "unsafe-workflow-path" ||
191
+ message === "workflow-source-changed" ||
192
+ message === "unsupported-source" ||
193
+ message === "invalid-declaration")
194
+ return message;
195
+ return "workflow-read-failed";
196
+ }
197
+ async function descriptorEntries(folder) {
198
+ const handle = await openSource(folder, ".").catch(changedPathError);
199
+ try {
200
+ const expected = folder.components.at(-1)?.stats;
201
+ if (!expected ||
202
+ !sameIdentity(expected, await handle.stat({ bigint: true })))
203
+ throw new Error("workflow-source-changed");
204
+ return await readdir(`/proc/self/fd/${String(handle.fd)}`);
205
+ }
206
+ finally {
207
+ await handle.close();
208
+ }
209
+ }
210
+ export async function workflowFolderEntries(folder) {
211
+ const entries = process.platform === "darwin"
212
+ ? await readdir(`/.nofollow${folder.path}`).catch(changedPathError)
213
+ : await descriptorEntries(folder);
214
+ await verifyFolder(folder);
215
+ return entries;
216
+ }