@greenpandastudios/aug-cli 0.19.0 → 0.21.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 (628) hide show
  1. package/README.md +9 -6
  2. package/THIRD_PARTY_NOTICES.md +4 -0
  3. package/bin/aug.mjs +12 -2
  4. package/docs/about.md +27 -0
  5. package/docs/api/crypto.md +103 -69
  6. package/docs/api/io.md +35 -33
  7. package/docs/api/json.md +3 -3
  8. package/docs/api/memory.md +23 -19
  9. package/docs/api/time.md +10 -9
  10. package/docs/api/web.md +33 -38
  11. package/docs/assets/benchmarks/execution.svg +774 -637
  12. package/docs/assets/benchmarks/http.svg +278 -249
  13. package/docs/assets/benchmarks/improvements.svg +66 -66
  14. package/docs/assets/benchmarks/kernels-mobile.svg +1 -0
  15. package/docs/assets/benchmarks/kernels.svg +1 -0
  16. package/docs/assets/benchmarks/memory.svg +153 -153
  17. package/docs/benchmark-results.json +227089 -90363
  18. package/docs/compatibility.md +8 -8
  19. package/docs/contributing-benchmarks.md +68 -0
  20. package/docs/dev-containers.md +88 -0
  21. package/docs/dgx-consumers.json +122 -0
  22. package/docs/dgx-gyms.json +784 -0
  23. package/docs/dgx-kernels.json +1170 -0
  24. package/docs/dgx-performance.json +227849 -0
  25. package/docs/dgx-spark.md +64 -0
  26. package/docs/diagnostics.md +15 -5
  27. package/docs/docker.md +173 -12
  28. package/docs/editor.md +35 -0
  29. package/docs/example-projects.json +238 -24
  30. package/docs/examples/approved-design/counters.md +34 -126
  31. package/docs/examples/approved-design/dependencies/august/0.21.0/io/contracts.md +178 -0
  32. package/docs/examples/approved-design/domain/app.md +13 -77
  33. package/docs/examples/approved-design/domain/export.md +5 -19
  34. package/docs/examples/approved-design/domain/models.md +4 -23
  35. package/docs/examples/approved-design/domain/numbers.md +24 -114
  36. package/docs/examples/approved-design/index.md +16 -7
  37. package/docs/examples/approved-design/main.md +19 -96
  38. package/docs/examples/benchmark/index.md +6 -5
  39. package/docs/examples/benchmark/main.md +7 -35
  40. package/docs/examples/calls-benchmark/index.md +36 -0
  41. package/docs/examples/calls-benchmark/main.md +78 -0
  42. package/docs/examples/calls-benchmark/operations.md +60 -0
  43. package/docs/examples/cli-args/index.md +6 -5
  44. package/docs/examples/cli-args/main.md +6 -31
  45. package/docs/examples/collections/index.md +6 -5
  46. package/docs/examples/collections/main.md +7 -35
  47. package/docs/examples/collections-benchmark/index.md +6 -5
  48. package/docs/examples/collections-benchmark/main.md +7 -35
  49. package/docs/examples/cpu-benchmark/index.md +6 -5
  50. package/docs/examples/cpu-benchmark/main.md +5 -24
  51. package/docs/examples/developer-workflow/calculator.md +39 -186
  52. package/docs/examples/developer-workflow/dependencies/august/0.21.0/io/contracts.md +178 -0
  53. package/docs/examples/developer-workflow/index.md +16 -7
  54. package/docs/examples/developer-workflow/logging/console.md +10 -56
  55. package/docs/examples/developer-workflow/logging/export.md +4 -14
  56. package/docs/examples/developer-workflow/logging/logger.md +7 -43
  57. package/docs/examples/developer-workflow/main.md +17 -69
  58. package/docs/examples/drop/index.md +6 -5
  59. package/docs/examples/drop/main.md +7 -27
  60. package/docs/examples/drop/resource.md +7 -30
  61. package/docs/examples/errors/errors.md +7 -27
  62. package/docs/examples/errors/index.md +6 -5
  63. package/docs/examples/errors/main.md +7 -28
  64. package/docs/examples/errors-benchmark/index.md +36 -0
  65. package/docs/examples/errors-benchmark/main.md +92 -0
  66. package/docs/examples/errors-benchmark/operations.md +65 -0
  67. package/docs/examples/ffi/index.md +6 -5
  68. package/docs/examples/ffi/main.md +6 -20
  69. package/docs/examples/ffi/native.md +8 -34
  70. package/docs/examples/float-benchmark/index.md +35 -0
  71. package/docs/examples/float-benchmark/main.md +75 -0
  72. package/docs/examples/generic-di/dependencies/august/0.21.0/io/contracts.md +178 -0
  73. package/docs/examples/generic-di/index.md +6 -5
  74. package/docs/examples/generic-di/main.md +9 -39
  75. package/docs/examples/generic-di/types.md +23 -92
  76. package/docs/examples/generics/index.md +6 -5
  77. package/docs/examples/generics/main.md +10 -47
  78. package/docs/examples/generics/types.md +25 -100
  79. package/docs/examples/hello/app/export.md +4 -12
  80. package/docs/examples/hello/app/greeter.md +13 -91
  81. package/docs/examples/hello/dependencies/august/0.21.0/io/contracts.md +178 -0
  82. package/docs/examples/hello/index.md +17 -6
  83. package/docs/examples/hello/logging/console.md +10 -54
  84. package/docs/examples/hello/logging/export.md +4 -14
  85. package/docs/examples/hello/logging/logger.md +7 -45
  86. package/docs/examples/hello/main.md +9 -39
  87. package/docs/examples/http-benchmark/index.md +6 -5
  88. package/docs/examples/http-benchmark/main.md +6 -20
  89. package/docs/examples/http-benchmark/routes.md +9 -31
  90. package/docs/examples/index.md +63 -29
  91. package/docs/examples/interceptors/app.md +20 -139
  92. package/docs/examples/interceptors/dependencies/august/0.21.0/io/contracts.md +178 -0
  93. package/docs/examples/interceptors/index.md +6 -5
  94. package/docs/examples/interceptors/interceptors.md +24 -148
  95. package/docs/examples/interceptors/logging.md +13 -76
  96. package/docs/examples/interceptors/main.md +10 -65
  97. package/docs/examples/json-benchmark/data.md +4 -20
  98. package/docs/examples/json-benchmark/dependencies/packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +67 -0
  99. package/docs/examples/json-benchmark/index.md +7 -5
  100. package/docs/examples/json-benchmark/main-yaml.md +20 -0
  101. package/docs/examples/json-benchmark/main.md +10 -49
  102. package/docs/examples/list-benchmark/index.md +35 -0
  103. package/docs/examples/list-benchmark/main.md +78 -0
  104. package/docs/examples/map-churn-benchmark/index.md +35 -0
  105. package/docs/examples/map-churn-benchmark/main.md +106 -0
  106. package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/api.md +104 -0
  107. package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.md +54 -0
  108. package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/native.abi-json.md +48 -0
  109. package/docs/examples/native-blake3/hashing.md +93 -0
  110. package/docs/examples/native-blake3/index.md +38 -0
  111. package/docs/examples/native-blake3/main.md +73 -0
  112. package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/api.md +406 -0
  113. package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.md +51 -0
  114. package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.md +63 -0
  115. package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/native.abi-json.md +161 -0
  116. package/docs/examples/native-pytorch/index.md +38 -0
  117. package/docs/examples/native-pytorch/main.md +73 -0
  118. package/docs/examples/native-pytorch/tensors.md +123 -0
  119. package/docs/examples/native-sqlite/database.md +116 -0
  120. package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/api.md +284 -0
  121. package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.md +51 -0
  122. package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.md +75 -0
  123. package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/native.abi-json.md +119 -0
  124. package/docs/examples/native-sqlite/index.md +38 -0
  125. package/docs/examples/native-sqlite/main.md +73 -0
  126. package/docs/examples/native-zlib/compression.md +99 -0
  127. package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/api.md +153 -0
  128. package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.md +54 -0
  129. package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/native.abi-json.md +76 -0
  130. package/docs/examples/native-zlib/index.md +38 -0
  131. package/docs/examples/native-zlib/main.md +78 -0
  132. package/docs/examples/new-syntax/console.md +10 -52
  133. package/docs/examples/new-syntax/dependencies/august/0.21.0/io/contracts.md +178 -0
  134. package/docs/examples/new-syntax/greeter.md +13 -71
  135. package/docs/examples/new-syntax/index.md +6 -5
  136. package/docs/examples/new-syntax/logger.md +7 -43
  137. package/docs/examples/new-syntax/main.md +10 -53
  138. package/docs/examples/new-syntax/math.md +6 -24
  139. package/docs/examples/oidc-login/client/contracts.md +10 -61
  140. package/docs/examples/oidc-login/client/endpoints.md +32 -154
  141. package/docs/examples/oidc-login/client/export.md +5 -23
  142. package/docs/examples/oidc-login/client/login.md +234 -344
  143. package/docs/examples/oidc-login/client/logout.md +38 -143
  144. package/docs/examples/oidc-login/client/protocol.md +183 -304
  145. package/docs/examples/oidc-login/client/session.md +41 -149
  146. package/docs/examples/oidc-login/client/views.md +13 -58
  147. package/docs/examples/oidc-login/common/export.md +6 -26
  148. package/docs/examples/oidc-login/common/headers.md +42 -72
  149. package/docs/examples/oidc-login/common/keys.md +40 -164
  150. package/docs/examples/oidc-login/common/settings.md +24 -40
  151. package/docs/examples/oidc-login/common/views.md +7 -28
  152. package/docs/examples/oidc-login/dependencies/packages/@git/url_0eb7c89453c87681ed15/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/store.md +205 -0
  153. package/docs/examples/oidc-login/dependencies/packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +67 -0
  154. package/docs/examples/oidc-login/dependencies/packages/@git/url_897efafd565158fc4908/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +267 -0
  155. package/docs/examples/oidc-login/dependencies/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/contracts.md +415 -0
  156. package/docs/examples/oidc-login/dependencies/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/jose.md +271 -0
  157. package/docs/examples/oidc-login/dependencies/packages/@git/url_c092cd151499c4e1d8a1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +89 -0
  158. package/docs/examples/oidc-login/index.md +8 -7
  159. package/docs/examples/oidc-login/main-yaml.md +7 -0
  160. package/docs/examples/oidc-login/main.md +26 -153
  161. package/docs/examples/oidc-login/provider/authorization.md +232 -275
  162. package/docs/examples/oidc-login/provider/contracts.md +24 -146
  163. package/docs/examples/oidc-login/provider/credentials.md +24 -57
  164. package/docs/examples/oidc-login/provider/discovery.md +48 -122
  165. package/docs/examples/oidc-login/provider/export.md +11 -37
  166. package/docs/examples/oidc-login/provider/token.md +159 -237
  167. package/docs/examples/oidc-login/provider/userinfo.md +47 -102
  168. package/docs/examples/oidc-login/provider/views.md +13 -53
  169. package/docs/examples/ownership/counter.md +17 -63
  170. package/docs/examples/ownership/index.md +6 -5
  171. package/docs/examples/ownership/main.md +7 -30
  172. package/docs/examples/ownership-transfer/dependencies/august/0.21.0/io/contracts.md +178 -0
  173. package/docs/examples/ownership-transfer/index.md +6 -5
  174. package/docs/examples/ownership-transfer/main.md +10 -52
  175. package/docs/examples/ownership-transfer/resource.md +15 -67
  176. package/docs/examples/packages-app/dependencies/packages/@example/aug-math/0.1.0/arithmetic.md +12 -50
  177. package/docs/examples/packages-app/index.md +7 -6
  178. package/docs/examples/packages-app/main.md +7 -25
  179. package/docs/examples/packages-math/aug-package-json.md +1 -1
  180. package/docs/examples/packages-math/index.md +7 -6
  181. package/docs/examples/packages-math/src/arithmetic.md +12 -50
  182. package/docs/examples/packages-math/src/export.md +4 -12
  183. package/docs/examples/records-benchmark/data.md +55 -0
  184. package/docs/examples/records-benchmark/index.md +36 -0
  185. package/docs/examples/records-benchmark/main.md +85 -0
  186. package/docs/examples/startup-benchmark/index.md +6 -5
  187. package/docs/examples/startup-benchmark/main.md +5 -17
  188. package/docs/examples/strings-benchmark/index.md +35 -0
  189. package/docs/examples/strings-benchmark/main.md +76 -0
  190. package/docs/examples/tasks-benchmark/index.md +36 -0
  191. package/docs/examples/tasks-benchmark/main.md +89 -0
  192. package/docs/examples/tasks-benchmark/operations.md +58 -0
  193. package/docs/examples/visibility/counter.md +19 -67
  194. package/docs/examples/visibility/index.md +6 -5
  195. package/docs/examples/visibility/main.md +7 -31
  196. package/docs/examples/weather-api/forecasts.md +168 -0
  197. package/docs/examples/weather-api/index.md +42 -0
  198. package/docs/examples/weather-api/main-yaml.md +21 -0
  199. package/docs/examples/weather-api/main.md +65 -0
  200. package/docs/examples.json +10 -0
  201. package/docs/getting-started.md +105 -2
  202. package/docs/grammar.md +7 -5
  203. package/docs/guides/change-a-module.md +64 -0
  204. package/docs/guides/index.md +27 -0
  205. package/docs/gym-results.json +784 -0
  206. package/docs/implementation-map.md +6 -0
  207. package/docs/index.md +51 -29
  208. package/docs/kernel-results.json +1170 -0
  209. package/docs/language-conformance.md +7 -1
  210. package/docs/language-constructs.md +30 -22
  211. package/docs/language-design-audit.md +1 -1
  212. package/docs/learn/data-and-errors.md +77 -0
  213. package/docs/learn/index.md +30 -0
  214. package/docs/learn/modules-and-dependencies.md +73 -0
  215. package/docs/learn/state-and-tests.md +71 -0
  216. package/docs/learn/values-and-functions.md +62 -0
  217. package/docs/lesson-failures.json +8 -0
  218. package/docs/maintaining-docs.md +16 -4
  219. package/docs/native-implementation.md +276 -0
  220. package/docs/native-interop-llvm-plan.md +342 -0
  221. package/docs/native-package-examples.md +684 -0
  222. package/docs/native-packages.md +179 -0
  223. package/docs/packages.md +72 -126
  224. package/docs/performance.md +61 -68
  225. package/docs/production-readiness.md +16 -5
  226. package/docs/public/downloads/approved-design.zip +0 -0
  227. package/docs/public/downloads/benchmark.zip +0 -0
  228. package/docs/public/downloads/calls-benchmark.zip +0 -0
  229. package/docs/public/downloads/cli-args.zip +0 -0
  230. package/docs/public/downloads/collections-benchmark.zip +0 -0
  231. package/docs/public/downloads/collections.zip +0 -0
  232. package/docs/public/downloads/cpu-benchmark.zip +0 -0
  233. package/docs/public/downloads/developer-workflow.zip +0 -0
  234. package/docs/public/downloads/drop.zip +0 -0
  235. package/docs/public/downloads/errors-benchmark.zip +0 -0
  236. package/docs/public/downloads/errors.zip +0 -0
  237. package/docs/public/downloads/ffi.zip +0 -0
  238. package/docs/public/downloads/float-benchmark.zip +0 -0
  239. package/docs/public/downloads/generic-di.zip +0 -0
  240. package/docs/public/downloads/generics.zip +0 -0
  241. package/docs/public/downloads/hello.zip +0 -0
  242. package/docs/public/downloads/http-benchmark.zip +0 -0
  243. package/docs/public/downloads/interceptors.zip +0 -0
  244. package/docs/public/downloads/json-benchmark.zip +0 -0
  245. package/docs/public/downloads/list-benchmark.zip +0 -0
  246. package/docs/public/downloads/map-churn-benchmark.zip +0 -0
  247. package/docs/public/downloads/native-blake3.zip +0 -0
  248. package/docs/public/downloads/native-pytorch.zip +0 -0
  249. package/docs/public/downloads/native-sqlite.zip +0 -0
  250. package/docs/public/downloads/native-zlib.zip +0 -0
  251. package/docs/public/downloads/new-syntax.zip +0 -0
  252. package/docs/public/downloads/oidc-login.zip +0 -0
  253. package/docs/public/downloads/ownership-transfer.zip +0 -0
  254. package/docs/public/downloads/ownership.zip +0 -0
  255. package/docs/public/downloads/packages-app.zip +0 -0
  256. package/docs/public/downloads/packages-math.zip +0 -0
  257. package/docs/public/downloads/records-benchmark.zip +0 -0
  258. package/docs/public/downloads/startup-benchmark.zip +0 -0
  259. package/docs/public/downloads/strings-benchmark.zip +0 -0
  260. package/docs/public/downloads/tasks-benchmark.zip +0 -0
  261. package/docs/public/downloads/visibility.zip +0 -0
  262. package/docs/public/downloads/weather-api.zip +0 -0
  263. package/docs/qualification-results.md +66 -0
  264. package/docs/reference.md +47 -17
  265. package/docs/releasing.md +105 -16
  266. package/docs/research/code-to-natural-language.md +108 -0
  267. package/docs/research/ecosystem-workflow.md +41 -0
  268. package/docs/research/libtorch-native-qualification.md +115 -0
  269. package/docs/research/native-interop-llvm.md +197 -0
  270. package/docs/research/qualification-methods.md +15 -0
  271. package/docs/research/wiki-editorial-design.md +71 -0
  272. package/docs/roadmap.md +2 -1
  273. package/docs/safety-gyms.md +41 -0
  274. package/docs/specifications.md +27 -11
  275. package/docs/testing.md +19 -5
  276. package/docs/tooling.md +36 -22
  277. package/docs/weather-api.md +65 -0
  278. package/docs/web-library-gaps.md +1 -1
  279. package/docs/web.md +54 -18
  280. package/docs/writing-docs.md +49 -0
  281. package/examples/{hello/.aug-spec/august/0.19.0 → approved-design/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
  282. package/examples/approved-design/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  283. package/examples/approved-design/.aug-spec/manifest.json +3 -3
  284. package/examples/approved-design/counters.aug +5 -4
  285. package/examples/approved-design/counters.aug.md +25 -132
  286. package/examples/approved-design/domain/app.aug +2 -1
  287. package/examples/approved-design/domain/app.aug.md +10 -80
  288. package/examples/approved-design/domain/export.aug +1 -0
  289. package/examples/approved-design/domain/export.aug.md +4 -20
  290. package/examples/approved-design/domain/models.aug +1 -0
  291. package/examples/approved-design/domain/models.aug.md +3 -25
  292. package/examples/approved-design/domain/numbers.aug +3 -2
  293. package/examples/approved-design/domain/numbers.aug.md +19 -116
  294. package/examples/approved-design/main.aug +1 -0
  295. package/examples/approved-design/main.aug.md +12 -95
  296. package/examples/benchmark/.aug-spec/manifest.json +1 -1
  297. package/examples/benchmark/main.aug +1 -0
  298. package/examples/benchmark/main.aug.md +6 -36
  299. package/examples/cli-args/.aug-spec/manifest.json +1 -1
  300. package/examples/cli-args/main.aug +1 -0
  301. package/examples/cli-args/main.aug.md +5 -32
  302. package/examples/collections/.aug-spec/manifest.json +1 -1
  303. package/examples/collections/main.aug +1 -0
  304. package/examples/collections/main.aug.md +6 -36
  305. package/examples/{generic-di/.aug-spec/august/0.19.0 → developer-workflow/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
  306. package/examples/developer-workflow/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  307. package/examples/developer-workflow/.aug-spec/manifest.json +3 -3
  308. package/examples/developer-workflow/calculator.aug +3 -2
  309. package/examples/developer-workflow/calculator.aug.md +28 -189
  310. package/examples/developer-workflow/logging/console.aug +2 -1
  311. package/examples/developer-workflow/logging/console.aug.md +7 -57
  312. package/examples/developer-workflow/logging/export.aug +1 -0
  313. package/examples/developer-workflow/logging/export.aug.md +3 -15
  314. package/examples/developer-workflow/logging/logger.aug +1 -0
  315. package/examples/developer-workflow/logging/logger.aug.md +6 -46
  316. package/examples/developer-workflow/main.aug +1 -0
  317. package/examples/developer-workflow/main.aug.md +10 -68
  318. package/examples/drop/.aug-spec/manifest.json +1 -1
  319. package/examples/drop/main.aug +1 -0
  320. package/examples/drop/main.aug.md +6 -28
  321. package/examples/drop/resource.aug +1 -0
  322. package/examples/drop/resource.aug.md +6 -34
  323. package/examples/errors/.aug-spec/manifest.json +1 -1
  324. package/examples/errors/errors.aug +2 -1
  325. package/examples/errors/errors.aug.md +4 -27
  326. package/examples/errors/main.aug +1 -0
  327. package/examples/errors/main.aug.md +6 -29
  328. package/examples/ffi/.aug-spec/manifest.json +1 -1
  329. package/examples/ffi/main.aug +1 -0
  330. package/examples/ffi/main.aug.md +5 -21
  331. package/examples/ffi/native.aug +2 -1
  332. package/examples/ffi/native.aug.md +5 -35
  333. package/examples/{approved-design/.aug-spec/august/0.19.0 → generic-di/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
  334. package/examples/generic-di/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  335. package/examples/generic-di/.aug-spec/manifest.json +3 -3
  336. package/examples/generic-di/main.aug +1 -0
  337. package/examples/generic-di/main.aug.md +8 -40
  338. package/examples/generic-di/types.aug +3 -2
  339. package/examples/generic-di/types.aug.md +18 -97
  340. package/examples/generics/.aug-spec/manifest.json +1 -1
  341. package/examples/generics/main.aug +1 -0
  342. package/examples/generics/main.aug.md +9 -48
  343. package/examples/generics/types.aug +4 -3
  344. package/examples/generics/types.aug.md +18 -104
  345. package/examples/{developer-workflow/.aug-spec/august/0.19.0 → hello/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
  346. package/examples/hello/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  347. package/examples/hello/.aug-spec/manifest.json +3 -3
  348. package/examples/hello/app/export.aug +1 -0
  349. package/examples/hello/app/export.aug.md +3 -13
  350. package/examples/hello/app/greeter.aug +2 -1
  351. package/examples/hello/app/greeter.aug.md +10 -94
  352. package/examples/hello/logging/console.aug +2 -1
  353. package/examples/hello/logging/console.aug.md +7 -55
  354. package/examples/hello/logging/export.aug +1 -0
  355. package/examples/hello/logging/export.aug.md +3 -15
  356. package/examples/hello/logging/logger.aug +1 -0
  357. package/examples/hello/logging/logger.aug.md +6 -48
  358. package/examples/hello/main.aug +1 -0
  359. package/examples/hello/main.aug.md +8 -40
  360. package/examples/interceptors/.aug-spec/august/0.21.0/io/contracts.aug +37 -0
  361. package/examples/interceptors/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  362. package/examples/interceptors/.aug-spec/manifest.json +3 -3
  363. package/examples/interceptors/app.aug +3 -2
  364. package/examples/interceptors/app.aug.md +15 -141
  365. package/examples/interceptors/interceptors.aug +3 -2
  366. package/examples/interceptors/interceptors.aug.md +19 -152
  367. package/examples/interceptors/logging.aug +2 -1
  368. package/examples/interceptors/logging.aug.md +10 -79
  369. package/examples/interceptors/main.aug +1 -0
  370. package/examples/interceptors/main.aug.md +9 -66
  371. package/examples/native-blake3/.aug-spec/manifest.json +16 -0
  372. package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.1/native.abi.json +29 -0
  373. package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/api.aug +14 -0
  374. package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/api.aug.md +34 -0
  375. package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.aug +4 -0
  376. package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.aug.md +8 -0
  377. package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/native.abi.json +29 -0
  378. package/examples/native-blake3/AGENTS.md +5 -0
  379. package/examples/native-blake3/aug.lock.json +253 -0
  380. package/examples/native-blake3/hashing.aug +11 -0
  381. package/examples/native-blake3/hashing.aug.md +25 -0
  382. package/examples/native-blake3/main.aug +8 -0
  383. package/examples/native-blake3/main.aug.md +13 -0
  384. package/examples/native-pytorch/.aug-spec/manifest.json +18 -0
  385. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.1/native.abi.json +142 -0
  386. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.3/native.abi.json +142 -0
  387. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/api.aug +101 -0
  388. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/api.aug.md +160 -0
  389. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.aug +3 -0
  390. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.aug.md +8 -0
  391. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.aug +6 -0
  392. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.aug.md +13 -0
  393. package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/native.abi.json +142 -0
  394. package/examples/native-pytorch/AGENTS.md +5 -0
  395. package/examples/native-pytorch/aug.lock.json +309 -0
  396. package/examples/native-pytorch/main.aug +8 -0
  397. package/examples/native-pytorch/main.aug.md +13 -0
  398. package/examples/native-pytorch/tensors.aug +23 -0
  399. package/examples/native-pytorch/tensors.aug.md +35 -0
  400. package/examples/native-sqlite/.aug-spec/manifest.json +18 -0
  401. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.2/native.abi.json +100 -0
  402. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/api.aug +48 -0
  403. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/api.aug.md +91 -0
  404. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.aug +3 -0
  405. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.aug.md +8 -0
  406. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.aug +8 -0
  407. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.aug.md +22 -0
  408. package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/native.abi.json +100 -0
  409. package/examples/native-sqlite/AGENTS.md +5 -0
  410. package/examples/native-sqlite/aug.lock.json +236 -0
  411. package/examples/native-sqlite/database.aug +15 -0
  412. package/examples/native-sqlite/database.aug.md +27 -0
  413. package/examples/native-sqlite/main.aug +8 -0
  414. package/examples/native-sqlite/main.aug.md +13 -0
  415. package/examples/native-zlib/.aug-spec/manifest.json +16 -0
  416. package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.1/native.abi.json +57 -0
  417. package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/api.aug +29 -0
  418. package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/api.aug.md +54 -0
  419. package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.aug +4 -0
  420. package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.aug.md +8 -0
  421. package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/native.abi.json +57 -0
  422. package/examples/native-zlib/AGENTS.md +5 -0
  423. package/examples/native-zlib/aug.lock.json +236 -0
  424. package/examples/native-zlib/compression.aug +15 -0
  425. package/examples/native-zlib/compression.aug.md +27 -0
  426. package/examples/native-zlib/main.aug +10 -0
  427. package/examples/native-zlib/main.aug.md +13 -0
  428. package/examples/new-syntax/.aug-spec/august/0.21.0/io/contracts.aug +37 -0
  429. package/examples/new-syntax/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  430. package/examples/new-syntax/.aug-spec/manifest.json +3 -3
  431. package/examples/new-syntax/console.aug +2 -1
  432. package/examples/new-syntax/console.aug.md +7 -53
  433. package/examples/new-syntax/greeter.aug +2 -1
  434. package/examples/new-syntax/greeter.aug.md +10 -74
  435. package/examples/new-syntax/logger.aug +1 -0
  436. package/examples/new-syntax/logger.aug.md +6 -46
  437. package/examples/new-syntax/main.aug +1 -0
  438. package/examples/new-syntax/main.aug.md +9 -54
  439. package/examples/new-syntax/math.aug +2 -1
  440. package/examples/new-syntax/math.aug.md +3 -24
  441. package/examples/oidc-login/.aug-spec/manifest.json +13 -13
  442. package/examples/oidc-login/.aug-spec/packages/@git/url_0eb7c89453c87681ed15/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/store.aug.md +71 -0
  443. package/examples/oidc-login/.aug-spec/packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.aug.md +17 -0
  444. package/examples/oidc-login/.aug-spec/packages/@git/url_897efafd565158fc4908/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.aug.md +125 -0
  445. package/examples/oidc-login/.aug-spec/{august/0.19.0/crypto → packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40}/contracts.aug +12 -11
  446. package/examples/oidc-login/.aug-spec/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/contracts.aug.md +244 -0
  447. package/examples/oidc-login/.aug-spec/{august/0.19.0/crypto → packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40}/jose.aug +6 -5
  448. package/examples/oidc-login/.aug-spec/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/jose.aug.md +71 -0
  449. package/examples/oidc-login/.aug-spec/packages/@git/url_c092cd151499c4e1d8a1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.aug.md +30 -0
  450. package/examples/oidc-login/aug.lock.json +100 -0
  451. package/examples/oidc-login/client/contracts.aug +1 -0
  452. package/examples/oidc-login/client/contracts.aug.md +9 -66
  453. package/examples/oidc-login/client/endpoints.aug +6 -5
  454. package/examples/oidc-login/client/endpoints.aug.md +13 -145
  455. package/examples/oidc-login/client/export.aug +1 -0
  456. package/examples/oidc-login/client/export.aug.md +4 -24
  457. package/examples/oidc-login/client/login.aug +7 -6
  458. package/examples/oidc-login/client/login.aug.md +25 -303
  459. package/examples/oidc-login/client/logout.aug +5 -4
  460. package/examples/oidc-login/client/logout.aug.md +13 -135
  461. package/examples/oidc-login/client/protocol.aug +7 -6
  462. package/examples/oidc-login/client/protocol.aug.md +36 -283
  463. package/examples/oidc-login/client/session.aug +5 -4
  464. package/examples/oidc-login/client/session.aug.md +12 -139
  465. package/examples/oidc-login/client/views.aug +3 -2
  466. package/examples/oidc-login/client/views.aug.md +8 -57
  467. package/examples/oidc-login/common/export.aug +1 -0
  468. package/examples/oidc-login/common/export.aug.md +5 -27
  469. package/examples/oidc-login/common/headers.aug +4 -3
  470. package/examples/oidc-login/common/headers.aug.md +9 -67
  471. package/examples/oidc-login/common/keys.aug +6 -5
  472. package/examples/oidc-login/common/keys.aug.md +29 -165
  473. package/examples/oidc-login/common/settings.aug +2 -1
  474. package/examples/oidc-login/common/settings.aug.md +5 -39
  475. package/examples/oidc-login/common/views.aug +2 -1
  476. package/examples/oidc-login/common/views.aug.md +4 -28
  477. package/examples/oidc-login/main.aug +6 -16
  478. package/examples/oidc-login/main.aug.md +17 -146
  479. package/examples/oidc-login/main.yaml +7 -0
  480. package/examples/oidc-login/provider/authorization.aug +7 -6
  481. package/examples/oidc-login/provider/authorization.aug.md +23 -238
  482. package/examples/oidc-login/provider/contracts.aug +1 -0
  483. package/examples/oidc-login/provider/contracts.aug.md +23 -158
  484. package/examples/oidc-login/provider/credentials.aug +3 -2
  485. package/examples/oidc-login/provider/credentials.aug.md +9 -53
  486. package/examples/oidc-login/provider/discovery.aug +4 -3
  487. package/examples/oidc-login/provider/discovery.aug.md +11 -118
  488. package/examples/oidc-login/provider/export.aug +1 -0
  489. package/examples/oidc-login/provider/export.aug.md +6 -34
  490. package/examples/oidc-login/provider/token.aug +6 -5
  491. package/examples/oidc-login/provider/token.aug.md +16 -204
  492. package/examples/oidc-login/provider/userinfo.aug +4 -3
  493. package/examples/oidc-login/provider/userinfo.aug.md +10 -92
  494. package/examples/oidc-login/provider/views.aug +3 -2
  495. package/examples/oidc-login/provider/views.aug.md +8 -52
  496. package/examples/ownership/.aug-spec/manifest.json +1 -1
  497. package/examples/ownership/counter.aug +3 -2
  498. package/examples/ownership/counter.aug.md +12 -66
  499. package/examples/ownership/main.aug +1 -0
  500. package/examples/ownership/main.aug.md +6 -31
  501. package/examples/ownership-transfer/.aug-spec/august/0.21.0/io/contracts.aug +37 -0
  502. package/examples/ownership-transfer/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
  503. package/examples/ownership-transfer/.aug-spec/manifest.json +3 -3
  504. package/examples/ownership-transfer/main.aug +1 -0
  505. package/examples/ownership-transfer/main.aug.md +9 -53
  506. package/examples/ownership-transfer/resource.aug +2 -1
  507. package/examples/ownership-transfer/resource.aug.md +12 -71
  508. package/examples/packages/app/.aug-spec/manifest.json +1 -1
  509. package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug +2 -1
  510. package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug.md +9 -51
  511. package/examples/packages/app/main.aug +1 -0
  512. package/examples/packages/app/main.aug.md +6 -26
  513. package/examples/packages/math/.aug-spec/manifest.json +1 -1
  514. package/examples/packages/math/aug-package.json +1 -1
  515. package/examples/packages/math/src/arithmetic.aug +2 -1
  516. package/examples/packages/math/src/arithmetic.aug.md +9 -51
  517. package/examples/packages/math/src/export.aug +1 -0
  518. package/examples/packages/math/src/export.aug.md +3 -13
  519. package/examples/visibility/.aug-spec/manifest.json +1 -1
  520. package/examples/visibility/counter.aug +4 -3
  521. package/examples/visibility/counter.aug.md +12 -68
  522. package/examples/visibility/main.aug +1 -0
  523. package/examples/visibility/main.aug.md +6 -32
  524. package/examples/weather-api/.aug-spec/manifest.json +8 -0
  525. package/examples/weather-api/AGENTS.md +17 -0
  526. package/examples/weather-api/README.md +19 -0
  527. package/examples/weather-api/forecasts.aug +47 -0
  528. package/examples/weather-api/forecasts.aug.md +32 -0
  529. package/examples/weather-api/main.aug +4 -0
  530. package/examples/weather-api/main.aug.md +15 -0
  531. package/examples/weather-api/main.yaml +5 -0
  532. package/examples/weather-api/weather.http +5 -0
  533. package/native/compiler-packs.json +43 -0
  534. package/package.json +4 -4
  535. package/runtime/aug_http.c +48 -21
  536. package/runtime/aug_http_ir.c +30 -0
  537. package/runtime/aug_http_ir.h +18 -0
  538. package/runtime/aug_ir.c +209 -0
  539. package/runtime/aug_ir.h +89 -0
  540. package/runtime/aug_json.c +6 -1
  541. package/runtime/aug_runtime.c +82 -16
  542. package/runtime/aug_runtime.h +16 -2
  543. package/runtime/aug_tasks.c +38 -7
  544. package/runtime/aug_values.c +12 -6
  545. package/scripts/bootstrap-native.mjs +184 -106
  546. package/scripts/native-setup.mjs +98 -0
  547. package/scripts/native-toolchain.mjs +28 -0
  548. package/src/ast.js +1 -1
  549. package/src/builtins.js +1 -1
  550. package/src/checker.js +369 -100
  551. package/src/cli.js +198 -26
  552. package/src/codegen.js +32 -7
  553. package/src/compiler-packs.js +66 -0
  554. package/src/config.js +3 -3
  555. package/src/contracts.js +10 -0
  556. package/src/editor.js +86 -22
  557. package/src/fixes.js +90 -2
  558. package/src/formatter.js +23 -10
  559. package/src/git-http.js +137 -0
  560. package/src/git-packages.js +118 -0
  561. package/src/help.js +33 -24
  562. package/src/http-contracts.js +14 -0
  563. package/src/http-policies.js +5 -5
  564. package/src/ir-types.js +5 -0
  565. package/src/ir-verify.js +277 -0
  566. package/src/ir.js +962 -0
  567. package/src/libraries.js +1 -1
  568. package/src/llvm-debug.js +101 -0
  569. package/src/llvm-native.js +152 -0
  570. package/src/llvm-platform.js +20 -0
  571. package/src/llvm.js +781 -0
  572. package/src/lsp.js +14 -5
  573. package/src/native-artifacts.js +207 -0
  574. package/src/native-bindings.js +189 -0
  575. package/src/native-contracts.js +295 -0
  576. package/src/native-declarations.js +68 -0
  577. package/src/native-facts.js +63 -0
  578. package/src/native.js +19 -13
  579. package/src/navigation.js +4 -2
  580. package/src/openapi.js +5 -4
  581. package/src/package-locking.js +88 -0
  582. package/src/package-manager.js +267 -68
  583. package/src/parser.js +32 -6
  584. package/src/policies.js +3 -3
  585. package/src/project-init.js +78 -4
  586. package/src/project.js +11 -9
  587. package/src/runtime-abi.js +23 -0
  588. package/src/runtime-adapters.js +18 -0
  589. package/src/schemas.js +24 -17
  590. package/src/semantic.js +54 -5
  591. package/src/snippets.js +60 -0
  592. package/src/spec-hints.js +56 -0
  593. package/src/spec-tree.js +241 -0
  594. package/src/spec.js +669 -305
  595. package/docs/examples/approved-design/dependencies/august/0.19.0/io/contracts.md +0 -395
  596. package/docs/examples/developer-workflow/dependencies/august/0.19.0/io/contracts.md +0 -395
  597. package/docs/examples/generic-di/dependencies/august/0.19.0/io/contracts.md +0 -395
  598. package/docs/examples/hello/dependencies/august/0.19.0/io/contracts.md +0 -395
  599. package/docs/examples/interceptors/dependencies/august/0.19.0/io/contracts.md +0 -395
  600. package/docs/examples/json-benchmark/dependencies/august/0.19.0/json/contracts.md +0 -103
  601. package/docs/examples/new-syntax/dependencies/august/0.19.0/io/contracts.md +0 -395
  602. package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/contracts.md +0 -925
  603. package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/jose.md +0 -434
  604. package/docs/examples/oidc-login/dependencies/august/0.19.0/json/contracts.md +0 -103
  605. package/docs/examples/oidc-login/dependencies/august/0.19.0/memory/store.md +0 -374
  606. package/docs/examples/oidc-login/dependencies/august/0.19.0/time/contracts.md +0 -150
  607. package/docs/examples/oidc-login/dependencies/august/0.19.0/web/contracts.md +0 -532
  608. package/docs/examples/ownership-transfer/dependencies/august/0.19.0/io/contracts.md +0 -395
  609. package/examples/approved-design/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  610. package/examples/developer-workflow/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  611. package/examples/generic-di/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  612. package/examples/hello/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  613. package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug +0 -36
  614. package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  615. package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug +0 -36
  616. package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  617. package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/contracts.aug.md +0 -791
  618. package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/jose.aug.md +0 -266
  619. package/examples/oidc-login/.aug-spec/august/0.19.0/json/contracts.aug.md +0 -55
  620. package/examples/oidc-login/.aug-spec/august/0.19.0/memory/store.aug.md +0 -250
  621. package/examples/oidc-login/.aug-spec/august/0.19.0/time/contracts.aug.md +0 -96
  622. package/examples/oidc-login/.aug-spec/august/0.19.0/web/contracts.aug.md +0 -420
  623. package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug +0 -36
  624. package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
  625. /package/examples/oidc-login/.aug-spec/{august/0.19.0/memory → packages/@git/url_0eb7c89453c87681ed15/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/store.aug +0 -0
  626. /package/examples/oidc-login/.aug-spec/{august/0.19.0/json → packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/contracts.aug +0 -0
  627. /package/examples/oidc-login/.aug-spec/{august/0.19.0/web → packages/@git/url_897efafd565158fc4908/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/contracts.aug +0 -0
  628. /package/examples/oidc-login/.aug-spec/{august/0.19.0/time → packages/@git/url_c092cd151499c4e1d8a1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/contracts.aug +0 -0
package/src/spec.js CHANGED
@@ -1,13 +1,17 @@
1
+ import { callableResult, callableErrors } from "./contracts.js";
1
2
  import { createHash } from 'node:crypto';
2
3
  import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
4
  import { basename, dirname, join, relative, resolve } from 'node:path';
4
5
  import { fieldsOf, typeName } from "./ast.js";
5
- import { builtinFunctions, builtinProperties, collectionOperations, operationType } from "./builtins.js";
6
+ import { builtinFunctions, builtinProperties, builtinTypes, collectionOperations, operationType } from "./builtins.js";
6
7
  import { callableDocumentation } from "./documentation.js";
7
8
  import { javadocBefore } from "./javadoc.js";
8
9
  import { libraryChild, libraryRelative } from "./libraries.js";
9
10
  import { compilerVersion } from "./package-manager.js";
10
11
  import { tyName } from "./types.js";
12
+ import { action, attempt, branch, choice, coordinate, flow, loop, paragraph, renderSpecTree, scope, section, sequence, step } from "./spec-tree.js";
13
+ import { specHint } from "./spec-hints.js";
14
+ import { nativeFact, nativeDescription } from "./native-facts.js";
11
15
  const generated = '<!-- Generated by aug spec. Edit the August source, then regenerate. -->';
12
16
  const copied = '// Generated by aug spec. This is a copy of the installed dependency source.\n';
13
17
  const code = (value) => { const text = value.replaceAll('\n', '\\n'); const fence = '`'.repeat(1 + Math.max(0, ...(text.match(/`+/g) ?? []).map(part => part.length))); return fence + (text.startsWith('`') ? ' ' : '') + text + (text.endsWith('`') ? ' ' : '') + fence; };
@@ -16,13 +20,15 @@ const anchor = (name) => 'symbol-' + name;
16
20
  const hash = (text) => createHash('sha256').update(text).digest('hex');
17
21
  const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
18
22
  const unreachable = (node) => { throw new Error(`No specification renderer for ${node.kind}`); };
19
- /** Render checked code, never execute it. All paths and ordering are reproducible. */
23
+ const plain = (text) => text.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replaceAll('`', '');
24
+ /** Plan prose, managed source pointers, and versioned dependency copies without writes or execution. */
20
25
  export function generateSpecs(checked, options = {}) {
21
26
  if (checked.diagnostics.some(issue => issue.severity !== 'warning'))
22
27
  throw new Error('Fix compiler errors before generating specifications');
23
28
  const project = checked.project;
24
29
  const owned = options.files ?? [...project.files.values()].filter(file => !file.builtin && !file.package);
25
30
  const own = new Set(owned.map(file => file.path));
31
+ const hints = owned.map(specHint), offsets = new Map(hints.map(hint => [hint.file.path, hint.lineOffset]));
26
32
  const docs = new Map(), sources = new Map();
27
33
  for (const file of project.files.values()) {
28
34
  if (own.has(file.path)) {
@@ -38,13 +44,20 @@ export function generateSpecs(checked, options = {}) {
38
44
  sources.set(file.path, source);
39
45
  }
40
46
  const queue = [...owned].sort((a, b) => compare(a.path, b.path));
41
- const seen = new Set(), outputs = [];
47
+ const seen = new Set(), outputs = hints.map(hint => ({ path: hint.file.path, text: hint.text, source: hint.file.path, kind: 'source-hint' }));
48
+ const contracts = new Map();
49
+ for (const [provider, path] of checked.native.providerDescriptors) {
50
+ const at = provider.lastIndexOf('@'), name = provider.slice(0, at), version = provider.slice(at + 1);
51
+ const destination = join(project.root, '.aug-spec', 'packages', name, version, 'native.abi.json');
52
+ contracts.set(provider, destination);
53
+ outputs.push({ path: destination, text: readFileSync(path, 'utf8'), source: path, kind: 'native-descriptor' });
54
+ }
42
55
  for (let index = 0; index < queue.length; index++) {
43
56
  const file = queue[index];
44
57
  if (seen.has(file.path))
45
58
  continue;
46
59
  seen.add(file.path);
47
- const writer = new SpecWriter(checked, file, docs, sources, own, path => {
60
+ const writer = new SpecWriter(checked, file, docs, sources, own, offsets, contracts, path => {
48
61
  const dependency = project.files.get(path);
49
62
  if (dependency && !seen.has(path))
50
63
  queue.push(dependency);
@@ -61,19 +74,27 @@ class SpecWriter {
61
74
  docs;
62
75
  sources;
63
76
  own;
77
+ offsets;
78
+ contracts;
64
79
  enqueue;
65
80
  used = new Map();
66
81
  builtins = new Map();
67
82
  properties = new Map();
68
83
  locals = new Map();
69
- constructor(checked, file, docs, sources, own, enqueue) {
84
+ constructor(checked, file, docs, sources, own, offsets, contracts, enqueue) {
70
85
  this.checked = checked;
71
86
  this.file = file;
72
87
  this.docs = docs;
73
88
  this.sources = sources;
74
89
  this.own = own;
90
+ this.offsets = offsets;
91
+ this.contracts = contracts;
75
92
  this.enqueue = enqueue;
76
93
  }
94
+ nativeDescription(native) {
95
+ const contract = this.contracts.get(native.provider);
96
+ return nativeDescription(native, `[${code(native.descriptor)}](${url(relative(dirname(this.docs.get(this.file.path)), contract))})`);
97
+ }
77
98
  definition(name) { return this.checked.project.scopes.get(this.file.path)?.get(name); }
78
99
  use(def, method, field) {
79
100
  if (!def || def.file === this.file.path)
@@ -99,95 +120,166 @@ class SpecWriter {
99
120
  return `[${code(label)}](${url(relative(dirname(this.docs.get(this.file.path)), this.docs.get(def.file)))}#${encodeURIComponent(anchor(name))})`;
100
121
  }
101
122
  source(span) {
102
- return `[source](${url(relative(dirname(this.docs.get(this.file.path)), this.sources.get(span.file)))}#L${span.line + (this.own.has(span.file) ? 0 : 1)})`;
123
+ return `[source](${url(relative(dirname(this.docs.get(this.file.path)), this.sources.get(span.file)))}#L${span.line + (this.own.has(span.file) ? this.offsets.get(span.file) ?? 0 : 1)})`;
103
124
  }
104
- heading(name, span, level = 2) {
105
- return `<a id="${anchor(name)}"></a>\n\n${'#'.repeat(level)} ${code(name)}\n\n${this.source(span)}\n\n`;
125
+ heading(name, span, level, children, role) {
126
+ return section(code(name) + (role ? ' · ' + role : ''), level, children, anchor(name), this.source(span));
106
127
  }
107
128
  type(type, file = this.file.path) {
108
- const def = this.checked.project.scopes.get(file)?.get(type.name);
129
+ const enclosing = this.checked.project.files.get(file)?.items.filter(item => 'typeParams' in item && item.span.start <= type.span.start && item.span.end >= type.span.end) ?? [];
130
+ const parameter = enclosing.some(item => 'typeParams' in item && (item.typeParams.includes(type.name) || 'methods' in item && item.methods.some(method => method.span.start <= type.span.start && method.span.end >= type.span.end && method.typeParams.includes(type.name))));
131
+ const def = parameter ? undefined : this.checked.project.scopes.get(file)?.get(type.name);
109
132
  this.use(def);
110
133
  type.args.forEach(arg => this.type(arg, file));
111
134
  return def ? this.link(def, def.name, typeName(type)) : code(typeName(type));
112
135
  }
113
- notes(span, method, owner) {
114
- const doc = method ? callableDocumentation(this.checked.project, method, owner) : javadocBefore(this.file.source, span.start);
115
- return doc ? `**Author documentation**\n\n${doc.markdown}\n\n` : '';
136
+ notes(doc) {
137
+ if (!doc)
138
+ return '';
139
+ const prose = doc.markdown.split(/(?:^|\n\n)\*\*(?:Returns|Parameters|Throws|Deprecated:|See also:)/)[0].trim();
140
+ const extra = (doc.tags ?? []).filter(tag => tag.name === 'deprecated' || tag.name === 'see').map(tag => tag.name === 'deprecated' ? `Deprecated: ${tag.value}` : `See also: ${tag.value}`);
141
+ return [prose, ...extra].filter(Boolean).join(' ');
116
142
  }
117
- generics(node) {
118
- return node.typeParams.length ? 'Type parameters: ' + node.typeParams.map(name => code(name) +
143
+ generics(node, file = this.file.path) {
144
+ return node.typeParams.length ? 'The type parameters are ' + coordinate(node.typeParams.map(name => code(name) +
119
145
  (node.typeVariance?.[name] ? ` (${node.typeVariance[name] === 'out' ? 'produces values' : 'accepts values'})` : '') +
120
- (node.typeConstraints?.[name]?.length ? ` must satisfy ${node.typeConstraints[name].map(type => this.type(type)).join(' and ')}` : '')).join('; ') + '.\n\n' : '';
146
+ (node.typeConstraints?.[name]?.length ? ` which must satisfy ${coordinate(node.typeConstraints[name].map(type => this.type(type, file)))}` : ''))) + '.' : '';
121
147
  }
122
- inputs(params, fields = false, file = this.file.path) {
148
+ inputs(params, fields = false, file = this.file.path, descriptions = new Map()) {
123
149
  if (!params.length)
124
150
  return '';
125
- return '**Inputs**\n\n' + params.map(param => {
151
+ if (fields && params.length === 1 && params[0].injected) {
152
+ const param = params[0];
126
153
  this.locals.set(param.name, param.type);
127
- const parts = [`${code(param.label ?? param.name)} (${this.type(param.type, file)})`];
128
- if (param.injected)
129
- parts.push('injected; callers omit it');
130
- else if (param.source)
131
- parts.push(`read from the HTTP ${param.source.kind}${param.source.name ? ' named ' + code(param.source.name) : ''}${param.type.optional ? '; absent value becomes null' : ''}`);
132
- else
133
- parts.push(param.type.optional ? 'optional labeled input; omission becomes null' : 'required labeled input');
154
+ return 'The ' + code(param.name) + ' dependency is injected as ' + this.type(param.type, file) + ' and stored ' + (param.mutable ? 'mutably' : 'read-only') +
155
+ (param.name.startsWith('_') ? ' and privately' : '') + (param.ownership === 'own' ? ' with ownership transferred' : param.ownership === 'borrow' ? ' with permission to mutate it' : '') +
156
+ (descriptions.has(param.name) ? ' (' + descriptions.get(param.name).replace(/[.!?]$/, '') + ')' : '') + '.';
157
+ }
158
+ const describe = (group) => {
159
+ const param = group[0];
160
+ group.forEach(param => this.locals.set(param.name, param.type));
161
+ const name = coordinate(group.map(param => code(param.label ?? param.name))), type = this.type(param.type, file);
162
+ const simple = { int: group.length === 1 ? 'an integer' : 'integers', float: group.length === 1 ? 'a number' : 'numbers', string: group.length === 1 ? 'a string' : 'strings', bool: group.length === 1 ? 'a boolean' : 'booleans' };
163
+ let sentence = param.injected ? coordinate(group.map(param => code(param.name))) + ' (' + type + ')' : name + ' as ' + (simple[param.type.name] && !param.type.optional ? simple[param.type.name] : type);
164
+ if (param.source)
165
+ sentence += ` from the HTTP ${param.source.kind}${param.source.name ? ' ' + code(param.source.name) : ''}`;
134
166
  if (param.ownership === 'own')
135
- parts.push('transfers ownership');
167
+ sentence += ' with ownership transferred';
136
168
  else if (param.ownership === 'borrow')
137
- parts.push('allows exclusive mutation during the call');
169
+ sentence += ' with permission to mutate it during the call';
138
170
  if (fields)
139
- parts.push(`stored as ${code(param.name)}${param.name.startsWith('_') ? ' (private)' : ''}${param.mutable ? ' and mutable' : ' and read-only after initialization'}`);
140
- return '- ' + parts.join(' — ') + '.';
141
- }).join('\n') + '\n\n';
171
+ sentence += `, kept ${param.mutable ? 'mutable' : 'read-only'}${param.name.startsWith('_') ? ' and private' : ''}${param.label && param.label !== param.name ? ' as ' + code(param.name) : ''}`;
172
+ const description = descriptions.get(param.name);
173
+ if (description)
174
+ sentence += ' (' + description.replace(/[.!?]$/, '').replace(/^The /, 'the ') + ')';
175
+ return sentence;
176
+ };
177
+ const groups = (injected) => this.parameterGroups(params.filter(param => param.injected === injected), fields, descriptions).map(describe);
178
+ const supplied = groups(false), injected = groups(true);
179
+ const queries = params.filter(param => !param.injected && param.source?.kind === 'query');
180
+ const onlyQueries = queries.length && queries.length === params.filter(param => !param.injected).length;
181
+ const aliases = queries.filter(param => param.source?.name && param.source.name !== param.name);
182
+ const typedQueries = queries.filter(param => param.type.name !== 'string');
183
+ const suppliedText = onlyQueries ? 'It reads ' + coordinate(queries.map(param => code(param.source?.name ?? param.label ?? param.name))) + ' from the HTTP query.' +
184
+ (aliases.length ? ' ' + coordinate(aliases.map(param => code(param.source.name) + ' is called ' + code(param.name) + ' here')) + '.' : '') +
185
+ (typedQueries.length ? ' It parses ' + coordinate(typedQueries.map(param => code(param.name) + ' as ' + this.type(param.type, file))) + '.' : '') :
186
+ supplied.length ? 'It takes ' + coordinate(supplied) + '.' : '';
187
+ return [suppliedText, injected.length ? 'It gets ' + coordinate(injected) + ' from dependency injection.' : '',
188
+ params.some(param => param.type.optional) ? 'Omitted optional inputs are null.' : ''].filter(Boolean).join(' ');
142
189
  }
143
- contract(method) {
190
+ parameterGroups(params, fields = false, descriptions = new Map()) {
191
+ const groups = [];
192
+ const key = (value) => JSON.stringify([typeName(value.type), value.source, value.ownership, fields && value.mutable, fields && value.name.startsWith('_')]);
193
+ const alias = (value) => fields && value.label && value.label !== value.name;
194
+ for (const param of params) {
195
+ const previous = groups.at(-1)?.[0];
196
+ if (previous && key(previous) === key(param) && !descriptions.has(param.name) && !descriptions.has(previous.name) && !alias(param) && !alias(previous))
197
+ groups.at(-1).push(param);
198
+ else
199
+ groups.push([param]);
200
+ }
201
+ return groups;
202
+ }
203
+ contract(method, documentation) {
144
204
  const effects = this.checked.effectContracts.get(method);
145
205
  const layers = this.checked.interceptorPlans.get(method) ?? [];
146
- const errors = [...new Set([...method.throws.map(type => { this.type(type, method.span.file); return typeName(type); }), ...layers.flatMap(layer => layer.errors.map(tyName))])];
206
+ for (const type of method.throws)
207
+ this.type(type, method.span.file);
208
+ const errors = callableErrors(this.checked, method);
209
+ for (const error of this.checked.callableContracts.get(method)?.errors ?? [])
210
+ this.use(error.def);
147
211
  const changes = effects?.changes ?? method.changes ?? [];
148
212
  const uses = [...(effects?.uses.values() ?? method.uses ?? [])];
149
- let text = this.generics(method) + this.inputs(method.params, false, method.span.file);
150
- text += `Returns: ${method.returns.name === 'void' ? 'no value' : (method.returnOwnership === 'own' ? 'ownership of ' : '') + this.type(method.returns, method.span.file)}.\n\n`;
213
+ const result = [];
214
+ const generics = this.generics(method), inputs = this.inputs(method.params, false, method.span.file, documentation?.parameters);
215
+ if (generics)
216
+ result.push(paragraph(generics));
217
+ if (inputs)
218
+ result.push(paragraph(inputs));
219
+ const facts = [];
220
+ const returnNote = documentation?.tags?.find(tag => tag.name === 'return' || tag.name === 'returns')?.value ??
221
+ documentation?.markdown.match(/\*\*Returns\*\* ([^\n]+)/)?.[1];
222
+ // Implementations explain their actual returns below. Contracts need the
223
+ // result type; repeating it before every return adds noise to the narrative.
224
+ const resultType = callableResult(this.checked, method);
225
+ const reference = (type) => ({ name: type.name, args: type.args.map(reference), nullable: type.nullable, optional: type.optional, span: method.returns.span });
226
+ const returns = reference(resultType);
227
+ this.use(resultType.def);
228
+ this.type(returns, method.span.file);
229
+ if (returns.name !== 'void' && (!method.body || returnNote || method.returnOwnership === 'own'))
230
+ facts.push(`It returns ${(method.returnOwnership === 'own' ? 'ownership of ' : '') + this.type(returns, method.span.file)}` +
231
+ (returnNote ? ` — ${returnNote.replace(/[.!?]$/, '')}` : '') + '.');
151
232
  if (changes.length)
152
- text += 'May change: ' + changes.map(code).join(', ') + '.\n\n';
153
- if (uses.length)
154
- text += 'Capabilities: ' + uses.map(effect => {
155
- const def = 'capability' in effect ? effect.capability?.def : undefined;
156
- const target = def ?? this.definition(effect.source);
157
- const operation = target && 'methods' in target.node ? target.node.methods.find(method => method.name === effect.operation) : undefined;
158
- this.use(target, operation);
159
- return target ? this.link(target, operation ? `${target.name}.${operation.name}` : target.name, `${effect.source}.${effect.operation}`) : code(`${effect.source}.${effect.operation}`);
160
- }).join(', ') + '.\n\n';
161
- if (errors.length)
162
- text += 'Can fail with ' + errors.map(code).join(', ') + '. Callers must catch or propagate these errors.\n\n';
233
+ facts.push('It may change ' + coordinate(changes.map(code)) + '.');
234
+ const capabilities = uses.map(effect => {
235
+ const def = 'capability' in effect ? effect.capability?.def : undefined;
236
+ const target = def ?? this.definition(effect.source);
237
+ const operation = target && 'methods' in target.node ? target.node.methods.find(method => method.name === effect.operation) : undefined;
238
+ this.use(target, operation);
239
+ return target ? this.link(target, operation ? `${target.name}.${operation.name}` : target.name, `${effect.source}.${effect.operation}`) : code(`${effect.source}.${effect.operation}`);
240
+ });
241
+ if (capabilities.length && !method.body)
242
+ facts.push('It can call ' + coordinate(capabilities) + '.');
243
+ if (errors.length && !method.endpoint)
244
+ facts.push('Failures can raise ' + coordinate(errors.map(name => {
245
+ const note = documentation?.tags?.find(tag => (tag.name === 'throws' || tag.name === 'exception') && tag.value.startsWith(name + ' '))?.value.slice(name.length).trim() ??
246
+ documentation?.markdown.split('**Throws**\n')[1]?.split('\n\n')[0]?.split('\n').find(line => line.startsWith('- ' + code(name) + ': '))?.slice(('- ' + code(name) + ': ').length);
247
+ const def = this.checked.project.scopes.get(method.span.file)?.get(name);
248
+ return (def ? this.link(def) : code(name)) + (note ? ` (${note.replace(/[.!?]$/, '').replace(/^When\b/, 'when')})` : '');
249
+ })) + '.');
250
+ if (facts.length)
251
+ result.push(paragraph(facts.join(' ')));
163
252
  if (method.endpoint) {
164
253
  const endpoint = method.endpoint;
165
- text += `HTTP route: ${code(endpoint.method)} ${code(endpoint.path)}. ` +
166
- (endpoint.streams ? `Stream ${this.type(method.returns)} items. ` : `Use status ${endpoint.status} when the handler returns a body; a returned HttpResponse can set its own status. `) +
167
- 'An unhandled request failure returns status 500 and cancels its request tasks.\n\n';
254
+ if (endpoint.streams)
255
+ result.push(paragraph(`It streams ${this.type(method.returns)} items.`));
256
+ else if (endpoint.status !== 200)
257
+ result.push(paragraph(`A plain response body uses HTTP status ${endpoint.status}.`));
168
258
  if (endpoint.errors.length)
169
- text += 'Declared HTTP failures: ' + endpoint.errors.map(error => `${this.type(error.type)} returns status ${error.status}`).join('; ') + '.\n\n';
259
+ result.push(paragraph('The handler responds with ' + coordinate(endpoint.errors.map(error => `HTTP ${error.status} for ${this.type(error.type)}`)) + '.'));
260
+ const unmapped = errors.filter(name => !endpoint.errors.some(error => typeName(error.type) === name));
261
+ if (unmapped.length)
262
+ result.push(paragraph('It can also raise ' + coordinate(unmapped.map(code)) + '.'));
170
263
  }
171
- return text;
264
+ return result;
172
265
  }
173
266
  layers(node) {
174
267
  const layers = this.checked.interceptorPlans.get(node) ?? [];
175
268
  const policies = node.kind === 'function' ? this.checked.httpPolicies.get(node) ?? [] : [];
176
269
  if (!layers.length && !policies.length)
177
- return '';
178
- let text = '**Interceptors, in execution order**\n\n';
179
- policies.forEach((policy, index) => {
180
- text += `${index + 1}. ${this.policy(policy)}` +
181
- (node.kind === 'function' && policy.dependencies.length ? ' Use ' + policy.dependencies.map(index => code(node.params[index].name)).join(', ') + '.' : '') + '\n';
270
+ return [];
271
+ const steps = [];
272
+ policies.forEach(policy => {
273
+ steps.push(this.policy(policy) +
274
+ (node.kind === 'function' && policy.dependencies.length ? ' Use ' + policy.dependencies.map(index => code(node.params[index].name)).join(', ') + '.' : ''));
182
275
  });
183
- layers.forEach((layer, index) => {
276
+ layers.forEach(layer => {
184
277
  this.use(layer.definition, layer.around);
185
- text += `${index + policies.length + 1}. Call ${this.link(layer.definition, `${layer.definition.name}.around`)}. ` +
186
- 'It can call the next layer or finish with its own result or failure. ' +
187
- (layer.annotation.mappings.length ? 'Map ' + layer.annotation.mappings.map(mapping => code(mapping.source) + ' to ' + code(mapping.name)).join(', ') + '. ' : '') +
188
- 'Unselected inputs pass through.\n';
278
+ steps.push(`Call ${this.link(layer.definition, `${layer.definition.name}.around`)}.` +
279
+ (layer.annotation.mappings.length ? ' Map ' + layer.annotation.mappings.map(mapping => code(mapping.source) + ' to ' + code(mapping.name)).join(', ') + '.' : ''));
189
280
  });
190
- return text + '\nThe first layer wraps the remaining layers. HTTP policies run before wire decoding; custom interceptors run after decoding. Follow linked behavior to see its conditions, input changes, and calls to the next layer.\n\n';
281
+ return [paragraph('Layers run in the declared order. ' + steps.join(' ')),
282
+ ...(policies.length ? [paragraph('HTTP policies run before decoding; custom interceptors run after it.')] : [])];
191
283
  }
192
284
  policy(policy) {
193
285
  const options = policy.options;
@@ -202,10 +294,12 @@ class SpecWriter {
202
294
  case 'Compress': return 'Negotiate gzip. Respect existing Content-Encoding and bodyless statuses. Encode stream items as complete concatenated gzip members.';
203
295
  }
204
296
  }
205
- receiver(expr) {
206
- const checked = this.checked.expressionTypes.get(expr)?.def;
207
- if (checked)
208
- return checked;
297
+ receiver(expr, operation) {
298
+ const checked = this.checked.expressionTypes.get(expr);
299
+ if (checked?.def)
300
+ return checked.def;
301
+ if (checked?.kind === 'param')
302
+ return checked.bounds?.find(bound => bound.def && (!operation || this.method(bound.def, operation)))?.def;
209
303
  const ref = expr.kind === 'name' ? this.locals.get(expr.name) : undefined;
210
304
  if (ref)
211
305
  return this.definition(ref.name);
@@ -216,7 +310,7 @@ class SpecWriter {
216
310
  if (def?.node.kind === 'class')
217
311
  return def;
218
312
  if (def?.node.kind === 'function')
219
- return this.definition(def.node.returns.name);
313
+ return callableResult(this.checked, def.node).def;
220
314
  }
221
315
  return undefined;
222
316
  }
@@ -246,57 +340,196 @@ class SpecWriter {
246
340
  return def ? this.link(def) : code(expr.name);
247
341
  }
248
342
  case 'member': {
249
- const def = this.receiver(expr.object);
343
+ const def = this.receiver(expr.object, expr.name);
250
344
  this.use(def, undefined, expr.name);
251
345
  const name = expr.name, receiverType = this.checked.expressionTypes.get(expr.object)?.name;
252
346
  const property = receiverType && builtinProperties[receiverType]?.find(property => property.name === name);
253
347
  if (property)
254
348
  this.properties.set(receiverType + '.' + name, { ...property, type: this.checked.expressionTypes.get(expr) ? tyName(this.checked.expressionTypes.get(expr)) : property.type });
255
- return `${code(name)} of ${this.expression(expr.object)}`;
349
+ const object = this.expression(expr.object), path = this.memberPath(expr);
350
+ return path ? code(path) : `${code(name)} of ${object}`;
256
351
  }
257
352
  case 'unary': {
258
353
  if (expr.op === '!' && expr.value.kind === 'binary' && (expr.value.op === '==' || expr.value.op === '!='))
259
354
  return `${this.expression(expr.value.left, true)} ${expr.value.op === '==' ? 'does not equal' : 'equals'} ${this.expression(expr.value.right, true)}`;
355
+ if (expr.op === '!' && expr.value.kind === 'call')
356
+ return this.predicate(expr.value, false);
260
357
  return `${expr.op === '!' ? 'not' : 'the negative of'} (${this.expression(expr.value)})`;
261
358
  }
262
359
  case 'binary': {
263
360
  const words = { '+': 'plus', '-': 'minus', '*': 'times', '/': 'divided by', '==': 'equals', '!=': 'does not equal', '<': 'is less than', '>': 'is greater than', '<=': 'is at most', '>=': 'is at least', '&&': 'and', '||': 'or' };
264
361
  if (!words[expr.op])
265
362
  throw new Error(`No specification renderer for operator ${expr.op}`);
363
+ if (expr.right.kind === 'literal' && expr.right.value === null && ['==', '!='].includes(expr.op))
364
+ return this.expression(expr.left) + (expr.op === '==' ? ' is null' : ' is not null');
365
+ if (expr.right.kind === 'literal' && expr.right.value === 0 && ['>', '<'].includes(expr.op))
366
+ return this.expression(expr.left) + (expr.op === '>' ? ' is positive' : ' is negative');
266
367
  const textParts = this.stringParts(expr);
267
- if (expr.op === '+' && textParts.length >= 3)
268
- return 'text formed by joining ' + textParts.map(part => this.expression(part)).join(', ') + ' in order';
269
- const result = `${this.expression(expr.left, expr.left.kind === 'binary')} ${words[expr.op]} ${this.expression(expr.right, expr.right.kind === 'binary')}`;
368
+ if (expr.op === '+' && textParts.length >= 2) {
369
+ const text = textParts.map(part => part.kind === 'literal' && typeof part.value === 'string' ?
370
+ part.value.replaceAll('{', '{{').replaceAll('}', '}}') : '{' + plain(this.expression(part)) + '}').join('');
371
+ return 'the text ' + code(text);
372
+ }
373
+ const child = (value) => this.expression(value, value.kind === 'binary' && !(['&&', '||'].includes(expr.op) &&
374
+ (value.op === expr.op || ['==', '!=', '<', '>', '<=', '>='].includes(value.op))));
375
+ const result = `${child(expr.left)} ${words[expr.op]} ${child(expr.right)}`;
270
376
  return nested ? `(${result})` : result;
271
377
  }
272
378
  case 'collection': {
273
379
  if (expr.collection === 'Map')
274
380
  return 'a map with ' + expr.items.filter((_, index) => index % 2 === 0).map((key, index) => `${this.expression(key)} mapped to ${this.expression(expr.items[index * 2 + 1])}`).join('; ');
381
+ const first = expr.items[0];
382
+ if (expr.collection === 'List' && expr.items.length >= 3 && first?.kind === 'call' && first.callee.kind === 'name' && !this.locals.has(first.callee.name)) {
383
+ const name = first.callee.name, def = this.definition(name);
384
+ const labels = first.argLabels;
385
+ if (def?.node.kind === 'class' && def.node.record && labels.every(Boolean) && expr.items.every(item => item.kind === 'call' && item.callee.kind === 'name' && item.callee.name === name &&
386
+ item.typeArgs.map(typeName).join(',') === first.typeArgs.map(typeName).join(',') &&
387
+ item.args.every(arg => arg.kind === 'literal') && item.argLabels.length === labels.length &&
388
+ item.argLabels.every((label, index) => label === labels[index]))) {
389
+ this.call(first);
390
+ const rows = expr.items.map(item => code('(' + item.args.map(arg => arg.kind === 'literal' ? arg.numericText ?? JSON.stringify(arg.value) : '').join(', ') + ')'));
391
+ return `a list of ${expr.items.length} ${this.link(def)} records, with ${code('(' + labels.join(', ') + ')')} values of ${coordinate(rows)}, in that order`;
392
+ }
393
+ }
275
394
  return `a ${expr.collection === 'empty' ? 'context-typed empty collection' : expr.collection.toLowerCase()}` + (expr.items.length ? ' containing ' + expr.items.map(item => this.expression(item)).join(', ') : ' with no items');
276
395
  }
277
396
  case 'resolve': return `the instance provided for ${code(expr.name)}${expr.typeArgs.length ? ' with type arguments ' + expr.typeArgs.map(type => this.type(type)).join(', ') : ''}`;
278
- case 'call': return this.call(expr);
279
- case 'start': return `a child task that starts ${this.expression(expr.call)}; evaluate its receiver and inputs now, and run its operation concurrently`;
280
- case 'wait': return `the results of waiting for ${expr.tasks.map(task => this.expression(task)).join(' and ')}; keep input order when returning a tuple or a list, join cleanup, and propagate the first failure`;
397
+ case 'call': {
398
+ const action = this.call(expr);
399
+ const known = this.knownValue(expr);
400
+ if (known)
401
+ return known;
402
+ if (expr.callee.kind === 'member') {
403
+ const receiver = expr.callee.object, type = this.checked.expressionTypes.get(receiver)?.name;
404
+ if (!this.receiver(receiver) && ['string', 'Bytes', 'List', 'Set', 'Map', 'Tuple'].includes(type ?? '') && !expr.args.length) {
405
+ const value = this.expression(receiver);
406
+ if (expr.callee.name === 'bytes' && type === 'string')
407
+ return 'the UTF-8 bytes of ' + value;
408
+ if (expr.callee.name === 'base64url' && type === 'Bytes')
409
+ return 'the URL-safe base64 encoding of ' + value;
410
+ if (expr.callee.name === 'length')
411
+ return (type === 'string' || type === 'Bytes' ? 'the byte length of ' : 'the number of elements in ') + value;
412
+ }
413
+ }
414
+ const name = expr.callee.kind === 'name' ? expr.callee.name : '';
415
+ return action.startsWith('construct ') ? (/^[AEIOU]/i.test(name) ? 'an ' : 'a ') + action.slice('construct '.length) :
416
+ action.startsWith('call ') ? action.slice('call '.length) : action;
417
+ }
418
+ case 'start': return `a child task running ${this.expression(expr.call)} with its inputs captured now`;
419
+ case 'wait': return `the result of waiting for ${expr.tasks.map(task => this.expression(task)).join(' and ')}${expr.tasks.length > 1 ? ' in input order' : ''}; propagate failures`;
281
420
  case 'handle': {
282
- const plan = this.checked.actions.get(expr);
283
- return `a deferred HTTP form action for ${plan ? this.link(plan.endpoint) : this.expression(expr.call)}` +
284
- (expr.call.kind === 'call' && expr.call.args.length ? '; inputs: ' + expr.call.args.map((value, index) => (expr.call.kind === 'call' ? code(expr.call.argLabels[index] ?? (value.kind === 'name' ? value.name : String(index + 1))) + ' from ' : '') + this.expression(value)).join('; ') : '') +
285
- '; capture supplied values when rendering, and read form inputs when submitted; send the form to that endpoint with its declared HTTP method';
421
+ const plan = this.checked.actions.get(expr), endpoint = plan?.endpoint.node.kind === 'function' ? plan.endpoint.node.endpoint : undefined;
422
+ const captures = expr.call.kind === 'call' ? expr.call.args.filter(value => value.kind !== 'formInput').map(value => this.expression(value)) : [];
423
+ return endpoint ? `a form action that sends ${code(endpoint.method + ' ' + endpoint.path)} to ${this.link(plan.endpoint)} on submission` +
424
+ (captures.length ? ' with captured ' + coordinate(captures) : '') : `a form action for ${this.expression(expr.call)}`;
286
425
  }
287
- case 'formInput': return 'the checked form input supplied when the HTTP form is submitted';
426
+ case 'formInput': return 'the submitted form data';
288
427
  case 'markupText': return code(expr.text);
289
428
  case 'markup': {
290
429
  const def = this.definition(expr.tag);
291
430
  this.use(def);
292
- return (def ? 'the server component ' + this.link(def) : 'the HTML element ' + code(expr.tag || 'fragment')) +
431
+ const elements = { p: 'a paragraph', h1: 'a heading', h2: 'a heading', button: 'a button', a: 'a link' };
432
+ return (def ? 'the server component ' + this.link(def) : elements[expr.tag] ?? 'the HTML element ' + code(expr.tag || 'fragment')) +
293
433
  (expr.attributes.length ? ' with ' + expr.attributes.map(attribute => `${code(attribute.name)} = ${this.expression(attribute.value)}`).join(', ') : '') +
294
434
  (expr.children.length ? ' containing ' + expr.children.map(child => this.expression(child)).join(', ') : '') +
295
- ' (rendered on the server with embedded text escaped)';
435
+ ' with escaped text';
296
436
  }
297
437
  default: return unreachable(expr);
298
438
  }
299
439
  }
440
+ memberPath(expr) {
441
+ if (expr.kind === 'name' && !this.definition(expr.name))
442
+ return expr.name;
443
+ if (expr.kind === 'name' && this.locals.has(expr.name))
444
+ return expr.name;
445
+ if (expr.kind === 'member') {
446
+ const object = this.memberPath(expr.object);
447
+ return object ? object + '.' + expr.name : undefined;
448
+ }
449
+ }
450
+ argument(expr, label) {
451
+ const explicit = expr.args.findIndex((arg, index) => (expr.argLabels[index] ?? (arg.kind === 'name' ? arg.name : '')) === label);
452
+ if (explicit >= 0)
453
+ return expr.args[explicit];
454
+ const plan = this.checked.callPlans.get(expr);
455
+ const def = expr.callee.kind === 'name' ? this.definition(expr.callee.name) : expr.callee.kind === 'member' ? this.receiver(expr.callee.object, expr.callee.name) : undefined;
456
+ const method = def?.node.kind === 'function' ? def.node : expr.callee.kind === 'member' && def ? this.method(def, expr.callee.name) : undefined;
457
+ const params = method?.params ?? (def?.node.kind === 'class' ? def.node.fields : []);
458
+ const slot = params.findIndex(param => (param.label ?? param.name) === label);
459
+ const index = slot >= 0 ? plan?.sourceIndices[slot] : undefined;
460
+ return index !== undefined && index >= 0 ? expr.args[index] : undefined;
461
+ }
462
+ /** Interpret only compiler built-ins and the canonical standard-library contracts. */
463
+ standardOperation(expr) {
464
+ if (expr.callee.kind !== 'member')
465
+ return;
466
+ const def = this.receiver(expr.callee.object, expr.callee.name);
467
+ if (!def || !this.checked.project.files.get(def.file)?.builtin)
468
+ return;
469
+ return libraryRelative(this.checked.project.libraries, def.file).replaceAll('\\', '/') + ':' + def.name + '.' + expr.callee.name;
470
+ }
471
+ knownValue(expr) {
472
+ const arg = (label) => { const value = this.argument(expr, label); return value ? this.expression(value) : undefined; };
473
+ const receiver = expr.callee.kind === 'member' ? this.expression(expr.callee.object) : '';
474
+ switch (this.standardOperation(expr)) {
475
+ case 'crypto/contracts.aug:Crypto.random':
476
+ case 'crypto/contracts.aug:GnuTlsCrypto.random':
477
+ return `${arg('size')} random bytes from ${receiver}`;
478
+ case 'crypto/contracts.aug:Crypto.decodeBase64url':
479
+ case 'crypto/contracts.aug:GnuTlsCrypto.decodeBase64url':
480
+ return `${arg('input')} decoded as URL-safe base64 by ${receiver}`;
481
+ case 'time/contracts.aug:Clock.now':
482
+ case 'time/contracts.aug:SystemClock.now': return 'the current time from ' + receiver;
483
+ case 'memory/store.aug:ExpiringStore.take':
484
+ case 'memory/store.aug:MemoryStore.take':
485
+ return `the live value removed from ${receiver} under ${arg('key')}, using ${arg('now')} as the current time`;
486
+ case 'memory/store.aug:ExpiringStore.get':
487
+ case 'memory/store.aug:MemoryStore.get':
488
+ return `the live value in ${receiver} under ${arg('key')}, using ${arg('now')} as the current time`;
489
+ }
490
+ if (expr.callee.kind === 'member' && !this.receiver(expr.callee.object)) {
491
+ const type = this.checked.expressionTypes.get(expr.callee.object)?.name;
492
+ if (expr.callee.name === 'get' && type === 'List')
493
+ return `the item at index ${arg('index')} in ${receiver}`;
494
+ if (expr.callee.name === 'get' && type === 'Map')
495
+ return `the value under ${arg('key')} in ${receiver}`;
496
+ if (expr.callee.name === 'contains' && type === 'Map')
497
+ return `whether ${receiver} contains the key ${arg('key')}`;
498
+ if (expr.callee.name === 'contains' && type === 'Set')
499
+ return `whether ${receiver} contains ${arg('value')}`;
500
+ if (expr.callee.name === 'with' && type === 'Headers')
501
+ return `${receiver} with the header ${arg('name')} set to ${arg('value')}`;
502
+ }
503
+ if (expr.callee.kind === 'name') {
504
+ const def = this.definition(expr.callee.name);
505
+ if (def?.file && this.checked.project.files.get(def.file)?.builtin && libraryRelative(this.checked.project.libraries, def.file).replaceAll('\\', '/') === 'web/contracts.aug' && def.name === 'urlEncode')
506
+ return 'URL-encoded ' + arg('input');
507
+ if (this.checked.expressionTypes.get(expr)?.name === 'Html') {
508
+ const message = this.argument(expr, 'message');
509
+ if (message && expr.args.length === 1)
510
+ return this.expression(expr.callee) + ' showing ' + this.expression(message);
511
+ }
512
+ }
513
+ if (expr.callee.kind === 'name' && !this.definition(expr.callee.name) && expr.callee.name === 'HttpResponse') {
514
+ const body = arg('body'), status = arg('status') ?? '200', headers = arg('headers');
515
+ return `HTTP ${status.replaceAll('`', '')} with ${body ?? 'an empty body'}` + (headers ? ' and ' + headers + ' headers' : '');
516
+ }
517
+ }
518
+ predicate(expr, expected = true) {
519
+ this.call(expr);
520
+ if (expr.callee.kind === 'member' && !this.receiver(expr.callee.object) &&
521
+ this.checked.expressionTypes.get(expr.callee.object)?.name === 'string' && expr.callee.name === 'isToken') {
522
+ const min = this.argument(expr, 'min'), max = this.argument(expr, 'max');
523
+ return this.expression(expr.callee.object) + ` is ${expected ? '' : 'not '}a URL-safe ASCII token` +
524
+ (min && max ? ' with ' + this.expression(min) + ' to ' + this.expression(max) + ' characters' : '');
525
+ }
526
+ const operation = this.standardOperation(expr);
527
+ if (operation === 'crypto/contracts.aug:Crypto.equal' || operation === 'crypto/contracts.aug:GnuTlsCrypto.equal') {
528
+ const left = this.argument(expr, 'left'), right = this.argument(expr, 'right');
529
+ return this.expression(left) + ' and ' + this.expression(right) + (expected ? ' match' : ' differ') + ' when compared by ' + this.expression(expr.callee.kind === 'member' ? expr.callee.object : expr.callee);
530
+ }
531
+ return this.expression(expr) + ` returns ${expected ? 'true' : 'false'}`;
532
+ }
300
533
  call(expr) {
301
534
  if (expr.callee.kind === 'name' && ['List', 'Set', 'Map'].includes(expr.callee.name) && !this.definition(expr.callee.name)) {
302
535
  const name = expr.callee.name, types = expr.typeArgs.map(type => this.type(type));
@@ -305,13 +538,14 @@ class SpecWriter {
305
538
  name.toLowerCase() + ' of ' + (types[0] ?? 'any');
306
539
  return items.length ? 'a ' + label + ' containing ' + items.join(', ') : 'an empty ' + label;
307
540
  }
308
- let target, method;
541
+ let target, method, constructs = false;
309
542
  if (expr.callee.kind === 'name') {
310
543
  const name = expr.callee.name;
311
544
  const def = this.definition(expr.callee.name);
312
545
  this.use(def);
313
546
  if (def?.node.kind === 'class' && def.file !== this.file.path)
314
547
  this.used.get(def.id).constructed = true;
548
+ constructs = def?.node.kind === 'class' || !def && Object.hasOwn(builtinTypes, name);
315
549
  method = def?.node.kind === 'function' ? def.node : undefined;
316
550
  const builtin = !def && builtinFunctions.find(operation => operation.name === name);
317
551
  if (builtin)
@@ -320,7 +554,7 @@ class SpecWriter {
320
554
  }
321
555
  else if (expr.callee.kind === 'member') {
322
556
  const name = expr.callee.name;
323
- const def = this.receiver(expr.callee.object);
557
+ const def = this.receiver(expr.callee.object, expr.callee.name);
324
558
  method = def && this.method(def, expr.callee.name);
325
559
  this.use(def, method);
326
560
  const receiver = this.checked.expressionTypes.get(expr.callee.object);
@@ -335,7 +569,10 @@ class SpecWriter {
335
569
  // An inherited operation links to the interface that actually declares it.
336
570
  const owner = method && [...this.checked.project.definitions.values()].find(def => 'methods' in def.node && def.node.methods.includes(method));
337
571
  this.use(owner, method);
338
- target = (owner ? this.link(owner, `${owner.name}.${method.name}`) : code(expr.callee.name)) + ' on ' + this.expression(expr.callee.object);
572
+ const object = this.expression(expr.callee.object), path = this.memberPath(expr.callee);
573
+ target = owner ? this.link(owner, `${owner.name}.${method.name}`, path ?? expr.callee.name) : code(path ?? expr.callee.name);
574
+ if (!path)
575
+ target += ' on ' + object;
339
576
  }
340
577
  else
341
578
  target = this.expression(expr.callee);
@@ -346,12 +583,19 @@ class SpecWriter {
346
583
  const inputs = expr.args.map((arg, index) => {
347
584
  const slot = plan?.sourceIndices.indexOf(index);
348
585
  const label = expr.argLabels[index] ?? (slot !== undefined && slot >= 0 ? parameters[slot]?.label ?? parameters[slot]?.name : arg.kind === 'name' ? arg.name : undefined);
349
- return (label ? code(label) + ' = ' : '') + (arg.kind === 'binary' ? '(' + this.expression(arg) + ')' : this.expression(arg));
586
+ const same = arg.kind === 'name' && arg.name === label || arg.kind === 'member' && arg.name === label;
587
+ const value = this.expression(arg);
588
+ return label && !same ? code(label) + (arg.kind === 'literal' ? ' ' : ' from ') + value : value;
589
+ });
590
+ let text = `${constructs ? 'construct' : 'call'} ${target}` + (expr.typeArgs.length ? ' for ' + coordinate(expr.typeArgs.map(type => this.type(type))) : '') + (inputs.length ? ' with ' + coordinate(inputs) : '');
591
+ const injected = parameters.flatMap((param, index) => {
592
+ if (!param.injected)
593
+ return [];
594
+ const source = plan?.injectionSources?.[index] ?? plan?.bindingKeys[index] ?? typeName(param.type);
595
+ return [code(source) + (source === param.name ? '' : ' for ' + code(param.name))];
350
596
  });
351
- let text = `call ${target}` + (expr.typeArgs.length ? ' with type arguments ' + expr.typeArgs.map(type => this.type(type)).join(', ') : '') + (inputs.length ? ' with ' + inputs.join('; ') : '');
352
- const injected = parameters.flatMap((param, index) => param.injected ? [`${code(param.name)} from ${code(plan?.injectionSources?.[index] ?? plan?.bindingKeys[index] ?? typeName(param.type))}`] : []);
353
597
  if (injected.length)
354
- text += '; inject ' + injected.join(', ');
598
+ text += ' using injected ' + coordinate(injected);
355
599
  return text;
356
600
  }
357
601
  stringParts(expr) {
@@ -359,21 +603,55 @@ class SpecWriter {
359
603
  return [...this.stringParts(expr.left), ...this.stringParts(expr.right)];
360
604
  return [expr];
361
605
  }
362
- joinedText(target, value, depth) {
363
- const parts = this.stringParts(value);
364
- if (parts.length < 4)
606
+ joinedText(target, value) {
607
+ if (this.stringParts(value).length < 2)
365
608
  return;
366
- const indent = ' '.repeat(depth), child = ' '.repeat(depth + 1);
367
- return `${indent}- Build ${target} by joining these text parts without separators, in order:\n` +
368
- parts.map((part, index) => `${child}${index + 1}. ${this.expression(part)}\n`).join('');
609
+ return step(`It builds ${target} as ${this.expression(value)}.`);
610
+ }
611
+ headerFields(value) {
612
+ let current = value;
613
+ const calls = [];
614
+ while (current.kind === 'call' && current.callee.kind === 'member' && current.callee.name === 'with' &&
615
+ this.checked.expressionTypes.get(current.callee.object)?.name === 'Headers') {
616
+ const labels = current.args.map((arg, index) => current.kind === 'call' ?
617
+ current.argLabels[index] ?? (arg.kind === 'name' ? arg.name : '') : ''), name = labels.indexOf('name'), value = labels.indexOf('value');
618
+ if (name < 0 || value < 0)
619
+ return;
620
+ calls.unshift(current);
621
+ current = current.callee.object;
622
+ }
623
+ if (calls.length < 3)
624
+ return;
625
+ const operation = collectionOperations.Headers?.find(operation => operation.name === 'with');
626
+ if (operation)
627
+ this.builtins.set('Headers.with', operation);
628
+ return { base: current, fields: calls.map(call => {
629
+ const labels = call.args.map((arg, index) => call.argLabels[index] ?? (arg.kind === 'name' ? arg.name : ''));
630
+ return `${this.expression(call.args[labels.indexOf('name')])} to ${this.expression(call.args[labels.indexOf('value')])}`;
631
+ }) };
632
+ }
633
+ statements(body) {
634
+ const result = [];
635
+ for (let index = 0; index < body.length; index++) {
636
+ const stmt = body[index], nodes = this.statement(stmt), tail = body[index + 1];
637
+ // A terminal guard and final return are exact alternatives, even without a written else.
638
+ if (stmt.kind === 'if' && !stmt.otherwise.length && index === body.length - 2 && tail?.kind === 'return' &&
639
+ stmt.then.at(-1)?.kind === 'return' && nodes[0].kind === 'branch') {
640
+ nodes[0].otherwise = this.statement(tail);
641
+ index++;
642
+ }
643
+ result.push(...nodes);
644
+ }
645
+ return result;
369
646
  }
370
- statements(body, depth = 0) {
371
- if (!body.length)
372
- return ' '.repeat(depth) + '- Finish this block without another operation.\n';
373
- return body.map(stmt => this.statement(stmt, depth)).join('');
647
+ statement(stmt) {
648
+ const nodes = this.explainStatement(stmt);
649
+ // Source identities survive sentence aggregation. Two identical calls are still two facts.
650
+ nodes[0].source = `${stmt.span.file}:${stmt.span.start}:${stmt.span.end}`;
651
+ return nodes;
374
652
  }
375
- statement(stmt, depth) {
376
- const lead = ' '.repeat(depth) + '- ', nested = (body) => this.statements(body, depth + 1);
653
+ explainStatement(stmt) {
654
+ const nested = (body) => this.statements(body);
377
655
  switch (stmt.kind) {
378
656
  case 'assign': {
379
657
  if (stmt.target.kind === 'name') {
@@ -386,42 +664,133 @@ class SpecWriter {
386
664
  this.locals.set(stmt.target.name, { name: def.name, args: [], nullable: false, span: stmt.span });
387
665
  }
388
666
  }
389
- const target = this.expression(stmt.target) + (stmt.declaredType ? ' of type ' + this.type(stmt.declaredType) : '');
390
- return (this.joinedText(target, stmt.value, depth) ?? lead + `Set ${target} to ${this.expression(stmt.value)}.\n`) +
391
- (stmt.ownership === 'own' ? lead + 'This variable owns the value.\n' : '');
667
+ if (stmt.declaredType)
668
+ this.type(stmt.declaredType);
669
+ const target = this.expression(stmt.target) + (stmt.declaredType && !['int', 'float', 'string', 'bool'].includes(stmt.declaredType.name) ? ' of type ' + this.type(stmt.declaredType) : '');
670
+ const headers = this.headerFields(stmt.value);
671
+ const arithmetic = stmt.value.kind === 'binary' && ['+', '-'].includes(stmt.value.op) && stmt.value.left.kind === 'name' && stmt.target.kind === 'name' && stmt.target.name === stmt.value.left.name && ['int', 'float', 'c_int'].includes(this.checked.expressionTypes.get(stmt.value)?.name ?? '');
672
+ const headerLead = headers ? `set ${target} from ${this.expression(headers.base)} by adding these header fields in order` : undefined;
673
+ const getter = stmt.value.kind === 'call' && !stmt.value.args.length && stmt.value.callee.kind === 'name' && this.definition(stmt.value.callee.name)?.node.kind === 'function';
674
+ const explanation = headers ? sequence('It ' + headerLead.replace(/^set /, 'sets '), headers.fields, headerLead) :
675
+ getter ? step('It gets ' + target + ' from ' + this.expression(stmt.value) + '.') :
676
+ this.joinedText(target, stmt.value) ?? (arithmetic && stmt.value.kind === 'binary' ? action(stmt.value.op === '+' ? 'increase' : 'decrease', `${target} by ${this.expression(stmt.value.right)}`) : action('set', `${target} to ${this.expression(stmt.value)}`));
677
+ return stmt.ownership === 'own' ? [explanation, step(`${target} owns this value.`)] : [explanation];
678
+ }
679
+ case 'destructure': return [action('split', `${this.expression(stmt.value)} into ${coordinate(stmt.names.map(code))} in order`)];
680
+ case 'expr': {
681
+ if (stmt.expr.kind === 'literal' && stmt.expr.value === null)
682
+ return [action('continue', 'without an operation')];
683
+ if (stmt.expr.kind === 'call') {
684
+ const call = this.call(stmt.expr);
685
+ if (stmt.expr.callee.kind === 'name' && !this.definition(stmt.expr.callee.name)) {
686
+ if (stmt.expr.callee.name === 'print')
687
+ return [step('It prints ' + this.expression(stmt.expr.args[0]) + '.')];
688
+ if (stmt.expr.callee.name === 'assert')
689
+ return [step('The test requires ' + this.condition(stmt.expr.args[0]) + '.')];
690
+ }
691
+ if (stmt.expr.callee.kind === 'member') {
692
+ const receiver = stmt.expr.callee.object, def = this.receiver(receiver, stmt.expr.callee.name), method = def && this.method(def, stmt.expr.callee.name);
693
+ const type = this.checked.expressionTypes.get(receiver)?.name;
694
+ const arg = (name) => this.expression(this.argument(stmt.expr, name));
695
+ if (!def) {
696
+ if (type === 'List' && stmt.expr.callee.name === 'append')
697
+ return [step('It appends ' + arg('value') + ' to ' + this.expression(receiver) + '.')];
698
+ if (type === 'Set' && stmt.expr.callee.name === 'add')
699
+ return [step('It adds ' + arg('value') + ' to ' + this.expression(receiver) + '.')];
700
+ if (type === 'Map' && stmt.expr.callee.name === 'set')
701
+ return [step('It stores ' + arg('value') + ' in ' + this.expression(receiver) + ' under ' + arg('key') + '.')];
702
+ if (type === 'Map' && stmt.expr.callee.name === 'take')
703
+ return [step('It removes the key ' + arg('key') + ' from ' + this.expression(receiver) + '.')];
704
+ }
705
+ if (method && callableResult(this.checked, method).name === 'void' && method.params.filter(param => !param.injected).length === 1 && stmt.expr.args.length === 1) {
706
+ const owner = [...this.checked.project.definitions.values()].find(owner => 'methods' in owner.node && owner.node.methods.includes(method));
707
+ const target = this.link(owner, owner.name + '.' + method.name, this.memberPath(stmt.expr.callee) ?? method.name);
708
+ const plan = this.checked.callPlans.get(stmt.expr);
709
+ const injected = method.params.flatMap((param, index) => param.injected ? [code(plan?.injectionSources?.[index] ?? plan?.bindingKeys[index] ?? typeName(param.type))] : []);
710
+ return [step('It passes ' + this.expression(stmt.expr.args[0]) + ' to ' + target + (injected.length ? ', using injected ' + coordinate(injected) : '') + '.')];
711
+ }
712
+ }
713
+ const stored = this.standardOperation(stmt.expr);
714
+ if (stored === 'memory/store.aug:ExpiringStore.put' || stored === 'memory/store.aug:MemoryStore.put') {
715
+ const arg = (name) => this.expression(this.argument(stmt.expr, name));
716
+ return [step(`It stores ${arg('value')} in ${this.expression(stmt.expr.callee.kind === 'member' ? stmt.expr.callee.object : stmt.expr.callee)} under ${arg('key')}, expiring at ${arg('expires')}. The current time for this write is ${arg('now')}.`)];
717
+ }
718
+ if (call.startsWith('call '))
719
+ return [action('call', call.slice(5))];
720
+ if (call.startsWith('construct '))
721
+ return [action('construct', call.slice(10))];
722
+ }
723
+ return [action('evaluate', this.expression(stmt.expr))];
392
724
  }
393
- case 'destructure': return lead + `Split ${this.expression(stmt.value)} into ${stmt.names.map(code).join(', ')}, in that order.\n`;
394
- case 'expr': return lead + (stmt.expr.kind === 'literal' && stmt.expr.value === null ? 'Continue without another operation' : stmt.expr.kind === 'call' ? 'Call ' + this.call(stmt.expr).slice(5) : 'Evaluate ' + this.expression(stmt.expr)) + '.\n';
395
725
  case 'return': {
396
726
  if (!stmt.value)
397
- return lead + 'Finish this operation without a result.\n';
398
- const joined = this.joinedText('the return value', stmt.value, depth);
399
- return joined ? joined + lead + 'Return that value.\n' : lead + 'Return ' + this.expression(stmt.value) + '.\n';
727
+ return [action('return', 'without a value')];
728
+ const headers = this.headerFields(stmt.value);
729
+ if (headers) {
730
+ const lead = `return headers starting with ${this.expression(headers.base)} and adding these fields in order`;
731
+ return [sequence('It ' + lead.replace(/^return /, 'returns '), headers.fields, lead)];
732
+ }
733
+ return [action('return', this.expression(stmt.value))];
734
+ }
735
+ case 'throw': return [action('fail', 'with ' + this.expression(stmt.value))];
736
+ case 'yield': return [action('send', this.expression(stmt.value) + ' as the next stream item')];
737
+ case 'if': {
738
+ const node = branch(this.condition(stmt.test), nested(stmt.then), nested(stmt.otherwise));
739
+ if (node.kind === 'branch' && !stmt.otherwise.length && stmt.then.length === 1 && stmt.then[0].kind === 'throw' &&
740
+ stmt.then[0].value.kind === 'call' && stmt.then[0].value.args.length === 0)
741
+ node.requirement = this.requirement(stmt.test);
742
+ return [node];
743
+ }
744
+ case 'while': return [loop('While ' + this.condition(stmt.test), nested(stmt.body), 'Repeat this loop while its condition remains true.')];
745
+ case 'for': return [loop('For each ' + coordinate(stmt.names.map(code)) + ' in a snapshot of ' + this.expression(stmt.iterable), nested(stmt.body), 'Repeat these steps for each remaining item in the snapshot.')];
746
+ case 'match': {
747
+ const value = this.expression(stmt.value);
748
+ const terminal = (body) => ['return', 'throw'].includes(body.at(-1)?.kind ?? '');
749
+ // A null guard that exits is followed only by the non-null path. This is
750
+ // a sequential explanation, not a nested tour through match syntax.
751
+ if (stmt.cases.length === 2 && stmt.cases[0].pattern === 'null' && terminal(stmt.cases[0].body) && stmt.cases[1].pattern === 'some') {
752
+ const read = stmt.value.kind === 'call', second = stmt.cases[1];
753
+ const first = branch(read ? 'no value is found' : value + ' is null', nested(stmt.cases[0].body));
754
+ return [...(read ? [step('It obtains ' + value + '.')] : []), first,
755
+ step('The non-null ' + (read ? 'result' : value) + ' becomes ' + code(second.name) + '.'), ...nested(second.body)];
756
+ }
757
+ return [choice(value, stmt.cases.map(clause => {
758
+ const condition = clause.pattern === 'else' ? 'Otherwise' : clause.pattern === 'some' ? 'If ' + value + ' is not null, using ' + code(clause.name) + ' for it' :
759
+ clause.pattern === 'type' ? 'If ' + value + ' satisfies ' + this.type(clause.type) + ', using ' + code(clause.name) + ' for it' :
760
+ clause.pattern === 'literal' ? 'If ' + value + ' equals ' + this.expression(clause.literal) : 'If ' + value + ' is null';
761
+ return { condition, children: nested(clause.body) };
762
+ }))];
400
763
  }
401
- case 'throw': return lead + 'Fail with ' + this.expression(stmt.value) + '. Transfer control to a matching catch or propagate the failure.\n';
402
- case 'yield': return lead + 'Send ' + this.expression(stmt.value) + ' as the next stream item. Honor backpressure before producing more items.\n';
403
- case 'if': return lead + 'If ' + this.condition(stmt.test) + ':\n' + nested(stmt.then) + (stmt.otherwise.length ? lead + 'Otherwise:\n' + nested(stmt.otherwise) : '');
404
- case 'while': return lead + 'While ' + this.condition(stmt.test) + ', repeat:\n' + nested(stmt.body) + ' '.repeat(depth + 1) + '- Check the condition again before the next iteration.\n';
405
- case 'for': return lead + 'For each ' + stmt.names.map(code).join(' and ') + ' in a snapshot of ' + this.expression(stmt.iterable) + ', in iteration order:\n' + nested(stmt.body);
406
- case 'match': return lead + 'Select the matching case for ' + this.expression(stmt.value) + ':\n' + stmt.cases.map(clause => {
407
- const pattern = clause.pattern === 'else' ? 'Any remaining case' : clause.pattern === 'some' ? 'A present, non-null value, named ' + code(clause.name) :
408
- clause.pattern === 'type' ? 'A value that satisfies ' + this.type(clause.type) + ', named ' + code(clause.name) :
409
- clause.pattern === 'literal' ? this.expression(clause.literal) : 'A null value, including omitted optional input';
410
- return ' '.repeat(depth + 1) + '- ' + pattern + ':\n' + this.statements(clause.body, depth + 2);
411
- }).join('');
412
- case 'try': return lead + 'Try these operations:\n' + nested(stmt.body) + stmt.catches.map(clause => lead + 'If they fail with ' + this.type(clause.type) + ', name the failure ' + code(clause.name) + ' and recover:\n' + nested(clause.body)).join('') +
413
- (stmt.always ? lead + 'On both success and failure, perform cleanup:\n' + nested(stmt.always) : '');
414
- case 'unsafe': return lead + 'Enter an unsafe boundary. Native calls use their declared contracts; their foreign implementation is outside this specification:\n' + nested(stmt.body);
415
- case 'borrow': return lead + 'Grant exclusive mutable access to ' + code(stmt.name) + ' for this block, then end the borrow:\n' + nested(stmt.body);
416
- case 'scope': return lead + 'Create a dependency and task scope. Join its child tasks and release scoped values before leaving:\n' + nested(stmt.body);
417
- case 'lock': return lead + 'Lock ' + this.expression(stmt.value) + ', expose its mutable value as ' + code(stmt.name) + ', and release the lock on every exit:\n' + nested(stmt.body);
418
- case 'freeze': return lead + 'Freeze ' + this.expression(stmt.value) + ' as ' + code(stmt.name) + '. Share the immutable value without copying it.\n';
419
- case 'serve': return lead + 'Serve ' + stmt.names.map(name => { const def = this.definition(name); return def ? this.link(def) : code(name); }).join(', ') + ' on port ' + this.expression(stmt.port) + '.\n';
764
+ case 'try': return [attempt(nested(stmt.body), stmt.catches.map(clause => ({ error: this.type(clause.type), name: code(clause.name), children: nested(clause.body) })), stmt.always ? nested(stmt.always) : undefined)];
765
+ case 'unsafe': return [scope('Within an unsafe block', nested(stmt.body), 'Native operations must satisfy their declared C contracts.')];
766
+ case 'borrow': return [scope('With temporary permission to change ' + code(stmt.name), nested(stmt.body), '')];
767
+ case 'scope': return [scope('Within a task and ownership scope', nested(stmt.body), 'On leaving this scope, join its child tasks and release its local values.')];
768
+ case 'lock': return [scope('While holding the lock on ' + this.expression(stmt.value) + ' as mutable ' + code(stmt.name), nested(stmt.body), 'Release this lock when the block exits, including on return or failure.')];
769
+ case 'freeze': return [action('freeze', this.expression(stmt.value) + ' as ' + code(stmt.name)), step('The immutable value is shared without copying it.')];
770
+ case 'serve': return [action('serve', coordinate(stmt.names.map(name => { const def = this.definition(name); return def ? this.link(def) : code(name); })) + ' on port ' + this.expression(stmt.port))];
420
771
  default: return unreachable(stmt);
421
772
  }
422
773
  }
423
774
  condition(expr) {
424
- return this.expression(expr) + (['binary', 'unary'].includes(expr.kind) ? '' : ' is true');
775
+ return expr.kind === 'call' ? this.predicate(expr) : this.expression(expr) + (['binary', 'unary'].includes(expr.kind) ? '' : ' is true');
776
+ }
777
+ requirement(expr, nested = false) {
778
+ if (expr.kind === 'unary' && expr.op === '!')
779
+ return this.condition(expr.value);
780
+ if (expr.kind === 'binary') {
781
+ if (expr.op === '||' || expr.op === '&&') {
782
+ if (expr.op === '&&' && expr.left.kind === 'binary' && expr.right.kind === 'binary' && expr.left.op === '!=' && expr.right.op === '!=' &&
783
+ this.memberPath(expr.left.left) === this.memberPath(expr.right.left) && this.memberPath(expr.left.left))
784
+ return this.expression(expr.left.left) + ' is either ' + this.expression(expr.left.right) + ' or ' + this.expression(expr.right.right);
785
+ const child = (value) => this.requirement(value, value.kind === 'binary' && ['&&', '||'].includes(value.op) && value.op !== expr.op);
786
+ const text = child(expr.left) + (expr.op === '||' ? ' and ' : ' or ') + child(expr.right);
787
+ return nested ? '(' + text + ')' : text;
788
+ }
789
+ const inverse = { '==': 'does not equal', '!=': 'equals', '<': 'is at least', '>': 'is at most', '<=': 'is greater than', '>=': 'is less than' };
790
+ if (inverse[expr.op])
791
+ return this.expression(expr.left, true) + ' ' + inverse[expr.op] + ' ' + this.expression(expr.right, true);
792
+ }
793
+ return expr.kind === 'call' ? this.predicate(expr, false) : this.expression(expr) + ' is false';
425
794
  }
426
795
  binding(binding) {
427
796
  const info = this.checked.bindings.find(info => info.declaration === binding);
@@ -429,256 +798,232 @@ class SpecWriter {
429
798
  this.use(def);
430
799
  const lifetime = binding.lifetime ?? info?.lifetime;
431
800
  const key = binding.key + (binding.keyTypeArgs.length ? '<' + binding.keyTypeArgs.map(type => typeName(type)).join(', ') + '>' : '');
432
- return (`Provide ${def ? this.link(def, def.name, typeName(binding.target)) : this.type(binding.target)} when ${code(key)} is requested. ` +
433
- (lifetime === 'shared' ? 'Reuse one instance. ' : lifetime === 'scoped' ? 'Reuse one instance per explicit scope. ' : lifetime === 'fresh' ? 'Construct a fresh instance for each resolve. ' : 'Use a fresh instance when this provider retains state; otherwise reuse one instance. ') +
434
- (binding.sharedMutation ? 'Permit explicit shared mutation. ' : '') +
435
- (info?.dependencies.length ? 'Required dependencies: ' + info.dependencies.map(code).join(', ') + '. ' : '')).trimEnd() + '\n';
801
+ return (`${code(key)} is provided by ${def ? this.link(def, def.name, typeName(binding.target)) : this.type(binding.target)}. ` +
802
+ (lifetime === 'shared' ? 'The same instance is shared. ' : lifetime === 'scoped' ? 'Each scope shares one instance. ' : lifetime === 'fresh' ? 'Each resolve creates a new instance. ' : 'Stateless instances are reused; stateful instances are created for each resolve. ') +
803
+ (binding.sharedMutation ? 'Shared mutation is allowed. ' : '') +
804
+ (info?.dependencies.length ? 'It requires bindings for ' + coordinate(info.dependencies.map(code)) + '. ' : '')).trimEnd();
436
805
  }
437
806
  callable(method, owner) {
438
807
  this.locals = new Map(owner && 'fields' in owner.node ? fieldsOf(owner.node).map(field => [field.name, field.type]) : []);
439
808
  const name = owner && owner.node.kind !== 'function' ? `${owner.name}.${method.name}` : method.name;
440
- return this.heading(name, method.span, owner && owner.node.kind !== 'function' ? 3 : 2) +
441
- (method.name.startsWith('_') ? 'Private to its defining scope.\n\n' : '') + this.contract(method) + this.layers(method) +
442
- (method.body ? '**What it does**\n\n' + this.statements(method.body) + '\n' : method.externC ? 'Native C operation. Its declared inputs, result, effects, and errors are the visible contract. The C implementation is outside this specification.\n\n' : 'Interface contract. A selected implementation supplies the behavior.\n\n') +
443
- this.notes(method.annotations?.[0]?.span ?? method.span, method, owner);
809
+ const documentation = callableDocumentation(this.checked.project, method, owner);
810
+ const children = [];
811
+ if (method.endpoint)
812
+ children.push(paragraph(code(method.name) + ' handles ' + code(method.endpoint.method + ' ' + method.endpoint.path) + '.'));
813
+ if (method.name.startsWith('_'))
814
+ children.push(paragraph('It is private to its defining scope.'));
815
+ const notes = this.notes(documentation);
816
+ if (notes)
817
+ children.push(paragraph(notes));
818
+ children.push(...this.contract(method, documentation), ...this.layers(method));
819
+ if (method.body)
820
+ children.push(flow(this.statements(method.body)));
821
+ else if (method.externC) {
822
+ const native = nativeFact(this.checked, method);
823
+ children.push(paragraph(native ? this.nativeDescription(native) : 'Native C implementation; only its declared contract is visible here.'));
824
+ }
825
+ return this.heading(name, method.span, owner && owner.node.kind !== 'function' ? 3 : 2, children);
826
+ }
827
+ exportLine(item) {
828
+ const folder = this.file.builtin ? libraryChild(this.checked.project.libraries, dirname(this.file.path), item.name) : join(dirname(this.file.path), item.name);
829
+ const file = item.folder ? join(folder, 'export.aug') : join(dirname(this.file.path), item.from + '.aug');
830
+ this.enqueue(file);
831
+ return `Export ${item.folder ? 'the folder ' : 'the declaration '}${code(item.name)} from [${code(basename(file))}](${url(relative(dirname(this.docs.get(this.file.path)), this.docs.get(file)))}${item.folder ? '' : '#' + encodeURIComponent(anchor(item.name))}).`;
832
+ }
833
+ providerLine(item) {
834
+ if (item.kind === 'bind')
835
+ return this.binding(item);
836
+ const def = this.definition(item.name);
837
+ return 'Include providers from ' + (def ? this.link(def) : code(item.name)) + '.';
444
838
  }
445
839
  declaration(item) {
446
840
  switch (item.kind) {
447
- case 'import': return '';
448
- case 'export': {
449
- const folder = this.file.builtin ? libraryChild(this.checked.project.libraries, dirname(this.file.path), item.name) : join(dirname(this.file.path), item.name);
450
- const file = item.folder ? join(folder, 'export.aug') : join(dirname(this.file.path), item.from + '.aug');
451
- this.enqueue(file);
452
- return `- Export ${item.folder ? 'the folder ' : 'the declaration '}${code(item.name)} from [${code(basename(file))}](${url(relative(dirname(this.docs.get(this.file.path)), this.docs.get(file)))}${item.folder ? '' : '#' + encodeURIComponent(anchor(item.name))}).\n`;
841
+ case 'resource': {
842
+ const native = nativeFact(this.checked, item);
843
+ return this.heading(item.name, item.span, 2, [paragraph(native ? this.nativeDescription(native) : code(item.name) + ' is an opaque native resource with an unresolved package release contract.')], 'native resource');
453
844
  }
454
845
  case 'function': return this.callable(item);
455
846
  case 'class': {
456
847
  const def = this.definition(item.name);
457
848
  this.locals = new Map();
458
- let text = this.heading(item.name, item.span) + `${item.record ? 'Immutable record' : 'Behavioral class'}${item.name.startsWith('_') ? ', private to this file' : ''}.\n\n` + this.generics(item) +
459
- (item.implements.length ? 'Satisfies ' + item.implements.map(type => { const def = this.definition(type.name); return def ? this.link(def) : this.type(type); }).join(', ') + '.\n\n' : '') +
460
- this.notes(item.annotations?.[0]?.span ?? item.span) + this.inputs(item.fields, true) + this.layers(item);
461
- if (item.validationErrors?.length)
462
- text += 'Construction can fail with ' + item.validationErrors.map(type => this.type(type)).join(', ') + '.\n\n';
849
+ const documentation = javadocBefore(this.file.source, item.annotations?.[0]?.span.start ?? item.span.start);
850
+ const children = [];
851
+ const generics = this.generics(item), notes = this.notes(documentation);
852
+ const inputs = this.inputs(item.fields, true, this.file.path, documentation?.parameters);
853
+ const implementsText = item.implements.length ? 'It implements ' + coordinate(item.implements.map(type => this.type(type))) + '.' : '';
854
+ const introduction = [notes, implementsText, item.name.startsWith('_') ? 'It is private to this file.' : '', generics].filter(Boolean).join(' ');
855
+ if (introduction)
856
+ children.push(paragraph(introduction));
857
+ if (inputs)
858
+ children.push(paragraph(inputs));
859
+ children.push(...this.layers(item));
860
+ const errors = this.checked.constructorContracts.get(item)?.errors;
861
+ if (errors?.length)
862
+ children.push(paragraph('Construction can fail with ' + errors.map(type => { this.use(type.def); return type.def ? this.link(type.def) : code(tyName(type)); }).join(', ') + '.'));
863
+ else if (item.validationErrors?.length)
864
+ children.push(paragraph('Construction can fail with ' + item.validationErrors.map(type => this.type(type)).join(', ') + '.'));
463
865
  if (item.stateFields?.length)
464
- text += '**Field initialization**\n\n' + item.stateFields.map(field => { this.locals.set(field.name, field.type); return `- Initialize ${code(field.name)} of type ${this.type(field.type)} to ${this.expression(field.initializer)}. ${field.mutable ? 'Mutable' : 'Read-only'} storage${field.name.startsWith('_') ? ', private to this class' : ''}.\n`; }).join('') + '\n';
866
+ children.push(...item.stateFields.map(field => { this.locals.set(field.name, field.type); return paragraph(`The ${field.mutable ? 'mutable' : 'read-only'}${field.name.startsWith('_') ? ', private' : ''} field ${code(field.name)} has type ${this.type(field.type)} and starts as ${this.expression(field.initializer)}.`); }));
465
867
  if (item.constructorBody)
466
- text += this.heading(item.name + '.initialize', item.span, 3) + 'When execution reaches the original constructor, store inputs and initialize local fields, then run this block once before returning the object:\n\n' + this.statements(item.constructorBody) + '\n';
868
+ children.push(this.heading(item.name + '.initialize', item.span, 3, [flow(this.statements(item.constructorBody))]));
467
869
  if (!item.record) {
468
870
  const defaults = this.checked.defaults.get(def.id);
469
871
  const inherited = [...(defaults?.values() ?? [])].filter(info => !item.methods.some(method => method.name === info.method.name));
470
872
  if (inherited.length)
471
- text += '**Inherited default behavior**\n\n' + inherited.map(info => { const parent = this.checked.project.definitions.get(info.from); this.use(parent, info.method); return '- ' + (parent ? this.link(parent, `${parent.name}.${info.method.name}`) : code(info.method.name)) + '.\n'; }).join('') + '\n';
873
+ children.push(paragraph('It inherits the default implementations of ' + coordinate(inherited.map(info => { const parent = this.checked.project.definitions.get(info.from); this.use(parent, info.method); return parent ? this.link(parent, `${parent.name}.${info.method.name}`) : code(info.method.name); })) + '.'));
472
874
  }
473
- return text + item.methods.map(method => this.callable(method, def)).join('');
875
+ children.push(...item.methods.map(method => this.callable(method, def)));
876
+ return this.heading(item.name, item.span, 2, children, item.record ? 'immutable record' : 'class');
474
877
  }
475
878
  case 'interface':
476
879
  case 'interceptor': {
477
880
  const def = this.definition(item.name);
478
881
  this.locals = new Map();
479
- let text = this.heading(item.name, item.span) + (item.kind === 'interceptor' ? 'Function or constructor middleware' : item.capability ? 'Capability interface' : 'Interface') +
480
- (item.name.startsWith('_') ? ', private to this file' : '') + '.\n\n' + this.generics(item) + this.notes(item.span);
882
+ const generics = this.generics(item), notes = this.notes(javadocBefore(this.file.source, item.span.start));
883
+ const role = item.kind === 'interceptor' ? 'interceptor' : item.capability ? 'capability interface' : 'interface';
884
+ const introduction = [notes, item.name.startsWith('_') ? 'It is private to this file.' : '', generics].filter(Boolean).join(' ');
885
+ const children = introduction ? [paragraph(introduction)] : [];
481
886
  if (item.kind === 'interface' && item.extends.length)
482
- text += 'Inherit contracts and default methods from ' + item.extends.map(type => { const def = this.definition(type.name); return def ? this.link(def) : this.type(type); }).join(', ') + '.\n\n';
483
- if (item.kind === 'interceptor')
484
- text += this.inputs(item.fields, true) + 'Create a fresh interceptor for each invocation. Its around operation can delegate once, change selected inputs, or short-circuit with a compatible result or failure.\n\n';
485
- return text + item.methods.map(method => this.callable(method, def)).join('');
887
+ children.push(paragraph('It inherits ' + coordinate(item.extends.map(type => this.type(type))) + '.'));
888
+ if (item.kind === 'interceptor') {
889
+ const inputs = this.inputs(item.fields, true);
890
+ if (inputs)
891
+ children.push(paragraph(inputs));
892
+ children.push(paragraph('Creates one interceptor per invocation. Its around operation may delegate once or finish early.'));
893
+ }
894
+ children.push(...item.methods.map(method => this.callable(method, def)));
895
+ return this.heading(item.name, item.span, 2, children, role);
486
896
  }
487
- case 'composition': return this.heading(item.name, item.span) + this.notes(item.span) + 'This composition declares these providers before startup:\n\n' + item.bindings.map(binding => '- ' + this.binding(binding)).join('') + '\n';
488
- case 'bind': return '- ' + this.binding(item);
489
- case 'include': {
490
- const def = this.definition(item.name);
491
- return '- Include the providers from ' + (def ? this.link(def) : code(item.name)) + ' before execution.\n';
897
+ case 'composition': {
898
+ const notes = this.notes(javadocBefore(this.file.source, item.span.start));
899
+ return this.heading(item.name, item.span, 2, [...(notes ? [paragraph(notes)] : []), paragraph('These providers are registered before startup. ' + item.bindings.map(binding => this.binding(binding)).join(' '))]);
492
900
  }
493
901
  case 'test': return this.tests(item);
494
- default: return this.statement(item, 0);
902
+ case 'bind':
903
+ case 'include': return paragraph(this.providerLine(item));
904
+ case 'export': return paragraph(this.exportLine(item));
905
+ case 'import': return paragraph('');
906
+ default: return flow(this.statement(item));
495
907
  }
496
908
  }
497
909
  tests(suite) {
498
910
  this.locals = new Map();
499
- let text = this.heading(this.testTitle(suite), suite.span) + 'Same-file ' + (suite.endpointSuite ? 'HTTP endpoint' : suite.functionSuite ? 'function' : 'class') + ' tests for ' + this.type(suite.type) + '. Each case gets isolated setup and dependency bindings.\n\n';
911
+ const children = [paragraph('Tests ' + this.type(suite.type) + '. Each case gets fresh setup and dependencies.')];
500
912
  for (const group of suite.groups) {
501
913
  this.locals = new Map();
502
- text += '### Group ' + code(group.name) + '\n\n';
914
+ const groupChildren = [];
503
915
  if (group.setup.length)
504
- text += '**Setup before each case**\n\n' + group.setup.map(entry => this.declaration(entry)).join('') + '\n';
916
+ groupChildren.push(paragraph('Setup for each case:'), flow(group.setup.flatMap(entry => entry.kind === 'bind' || entry.kind === 'include' ? [step(this.providerLine(entry))] : this.statement(entry))));
505
917
  const setupLocals = new Map(this.locals);
506
918
  for (const test of group.cases) {
507
919
  this.locals = new Map(setupLocals);
508
- text += '#### ' + code(test.name) + '\n\n' + this.source(test.span) + '\n\n';
920
+ const caseChildren = [];
509
921
  if (test.parameters)
510
- text += 'Run once for each row of ' + test.rows.map(row => this.expression(row)).join('; ') + '. Bind row positions to ' + test.parameters.map(code).join(', ') + '.\n\n';
511
- text += this.statements(test.body) + '\n';
922
+ caseChildren.push(paragraph('Run once for each row of ' + test.rows.map(row => this.expression(row)).join('; ') + '. Bind row positions to ' + test.parameters.map(code).join(', ') + '.'));
923
+ caseChildren.push(flow(this.statements(test.body)));
924
+ groupChildren.push(section(code(test.name), 4, caseChildren, undefined, this.source(test.span)));
512
925
  }
926
+ children.push(section(code(group.name), 3, groupChildren));
513
927
  }
514
- return text;
928
+ return this.heading(this.testTitle(suite), suite.span, 2, children);
515
929
  }
516
930
  testTitle(suite) {
517
931
  return 'test ' + suite.type.name + (suite.name === suite.type.name ? '' : ' ' + suite.name);
518
932
  }
519
933
  dependencySurface() {
520
934
  if (!this.used.size)
521
- return '';
935
+ return;
522
936
  const entries = [...this.used.values()].sort((a, b) => compare(relative(this.checked.project.root, this.docs.get(a.def.file)) + ':' + a.def.name, relative(this.checked.project.root, this.docs.get(b.def.file)) + ':' + b.def.name));
523
- let text = '## Dependencies used by this file\n\nOnly referenced types and operations appear here. Each name links to its complete specification.\n\n';
937
+ const groups = new Map();
524
938
  for (const entry of entries) {
525
939
  const def = entry.def, node = def.node;
526
- const kind = node.kind === 'class' && node.record ? 'record' : node.kind === 'interface' && node.capability ? 'capability interface' : node.kind;
527
- text += '### ' + this.link(def) + '\n\n' + kind[0].toUpperCase() + kind.slice(1);
528
940
  const imported = this.file.items.find(item => item.kind === 'import' && (this.checked.project.imports.get(item) ?? []).some(imported => imported.id === def.id));
529
- if (imported?.kind === 'import')
530
- text += ' from ' + code(imported.from.join('.')) + (imported.everything ? ' through import everything' : '');
531
- text += '.\n\n';
532
- if (node.kind === 'function')
533
- text += this.dependencyOperation(node, def);
534
- if (node.kind === 'class' && entry.constructed)
535
- text += '- Construct with ' + this.shortInputs(node.fields, def.file) + ' → ' + this.link(def) +
536
- (node.validationErrors?.length ? '; can fail with ' + node.validationErrors.map(type => this.type(type, def.file)).join(', ') : '') + '.\n';
537
- if (node.kind === 'class' && entry.fields.size)
538
- for (const name of [...entry.fields].sort(compare)) {
539
- const field = fieldsOf(node).find(field => field.name === name);
540
- if (field)
541
- text += '- Read ' + code(name) + ' (' + this.type(field.type, def.file) + ')' + (field.mutable ? '; its owner can change it' : '') + '.\n';
542
- }
543
- if ('methods' in node)
544
- for (const method of [...entry.operations].sort((a, b) => compare(a.name, b.name)))
545
- text += this.dependencyOperation(method, def);
546
- if (node.kind !== 'function' && !entry.constructed && !entry.fields.size && !entry.operations.size)
547
- text += 'Used as a type or provider.\n';
548
- text += '\n';
549
- }
550
- return text;
551
- }
552
- shortInputs(params, file) {
553
- return params.filter(param => !param.injected).map(param => code(param.label ?? param.name) + ': ' + this.type(param.type, file) +
554
- (param.source ? ' from HTTP ' + param.source.kind + (param.source.name ? ' ' + code(param.source.name) : '') : '')).join(', ') || 'no caller inputs';
555
- }
556
- dependencyOperation(method, owner) {
557
- const effects = this.checked.effectContracts.get(method), layers = this.checked.interceptorPlans.get(method) ?? [];
558
- const changes = effects?.changes ?? method.changes ?? [];
559
- const uses = [...(effects?.uses.values() ?? method.uses ?? [])];
560
- const errors = [...new Set([...method.throws.map(type => typeName(type)), ...layers.flatMap(layer => layer.errors.map(tyName))])];
561
- const name = owner.node.kind === 'function' ? owner.name : owner.name + '.' + method.name;
562
- let text = '- ' + this.link(owner, name) + (method.typeParams.length ? '<' + method.typeParams.map(code).join(', ') + '>' : '') +
563
- ' (' + this.shortInputs(method.params, method.span.file) + ') → ' + this.type(method.returns, method.span.file);
564
- const injected = method.params.filter(param => param.injected);
565
- if (injected.length)
566
- text += '; inject ' + injected.map(param => code(param.name) + ': ' + this.type(param.type, method.span.file)).join(', ');
567
- if (changes.length)
568
- text += '; changes ' + changes.map(code).join(', ');
569
- const otherUses = uses.filter(effect => effect.source !== owner.name || effect.operation !== method.name);
570
- if (otherUses.length)
571
- text += '; uses ' + otherUses.map(effect => {
572
- const target = ('capability' in effect ? effect.capability?.def : undefined) ?? this.checked.project.scopes.get(method.span.file)?.get(effect.source);
573
- const operation = target && 'methods' in target.node ? target.node.methods.find(method => method.name === effect.operation) : undefined;
574
- this.use(target, operation);
575
- return target ? this.link(target, operation ? `${target.name}.${operation.name}` : target.name, `${effect.source}.${effect.operation}`) : code(`${effect.source}.${effect.operation}`);
576
- }).join(', ');
577
- if (errors.length)
578
- text += '; can fail with ' + errors.map(code).join(', ');
579
- return text + '.\n';
580
- }
581
- fileOverview() {
582
- const lines = [];
583
- const declaration = (name) => `[${code(name)}](${url(basename(this.docs.get(this.file.path)))}#${encodeURIComponent(anchor(name))})`;
584
- for (const item of this.file.items) {
585
- switch (item.kind) {
586
- case 'class':
587
- lines.push(`- ${declaration(item.name)} is ${item.record ? 'an immutable record' : 'a class'}${item.implements.length ? ' implementing ' + item.implements.map(type => code(typeName(type))).join(', ') : ''}.`);
588
- break;
589
- case 'interface':
590
- lines.push(`- ${declaration(item.name)} is ${item.capability ? 'a capability interface' : 'an interface'}.`);
591
- break;
592
- case 'interceptor':
593
- lines.push(`- ${declaration(item.name)} is an interceptor.`);
594
- break;
595
- case 'function':
596
- lines.push(`- ${declaration(item.name)} ${item.endpoint ? `handles ${code(item.endpoint.method)} ${code(item.endpoint.path)}` : 'is a function'}${item.returns.name === 'void' ? '' : ` returning ${code(typeName(item.returns))}`}.`);
597
- break;
598
- case 'composition':
599
- lines.push(`- ${declaration(item.name)} declares ${item.bindings.length} ${item.bindings.length === 1 ? 'provider' : 'providers'}.`);
600
- break;
601
- case 'bind': break;
602
- case 'include':
603
- lines.push(`- Include providers from ${code(item.name)}.`);
604
- break;
605
- case 'export':
606
- lines.push(`- Export ${code(item.name)} from this folder.`);
607
- break;
608
- case 'test':
609
- lines.push(`- ${declaration(this.testTitle(item))} is a same-file test suite.`);
610
- break;
611
- default: break;
612
- }
941
+ const origin = imported?.kind === 'import' ? code(imported.from.join('.')) + (imported.everything ? ' through import everything' : '') : '';
942
+ const names = [this.link(def)];
943
+ const members = [...entry.operations].sort((a, b) => compare(a.name, b.name)).map(method => this.link(def, def.name + '.' + method.name, method.name));
944
+ members.push(...[...entry.fields].sort(compare).map(name => code(name)));
945
+ if (members.length)
946
+ names[0] += ' (' + coordinate(members) + ')';
947
+ const group = groups.get(origin) ?? [];
948
+ group.push(...names);
949
+ groups.set(origin, group);
613
950
  }
614
- const providers = this.file.items.filter(item => item.kind === 'bind');
615
- if (providers.length)
616
- lines.push(`- Register ${providers.length} ${providers.length === 1 ? 'dependency provider' : 'dependency providers'} before startup.`);
617
- const executable = this.file.items.filter(item => !['import', 'export', 'test', 'bind', 'include', 'class', 'interface', 'interceptor', 'function', 'composition'].includes(item.kind));
618
- if (executable.some(item => item.kind === 'try'))
619
- lines.push('- Run startup operations with checked error recovery.');
620
- for (const item of executable)
621
- if (item.kind === 'serve')
622
- lines.push(`- Serve ${item.names.length} HTTP ${item.names.length === 1 ? 'route' : 'routes'}.`);
623
- const otherSteps = executable.filter(item => item.kind !== 'try' && item.kind !== 'serve').length;
624
- if (otherSteps)
625
- lines.push(`- Run ${otherSteps} other startup ${otherSteps === 1 ? 'step' : 'steps'} in source order.`);
626
- return lines.length ? '## In this file\n\n' + lines.join('\n') + '\n\n' : '';
951
+ return section('Dependencies', 2, [...[...groups].map(([origin, names]) => paragraph('It uses ' + coordinate(names) + (origin ? ' from ' + origin : '') + '.'))]);
627
952
  }
628
953
  render() {
629
954
  const exports = this.file.items.filter(item => item.kind === 'export');
630
- const declarations = this.file.items.filter(item => ['class', 'interface', 'interceptor', 'function', 'composition'].includes(item.kind));
955
+ const declarations = this.file.items.filter(item => ['class', 'interface', 'interceptor', 'function', 'composition', 'resource'].includes(item.kind));
631
956
  const providers = this.file.items.filter(item => item.kind === 'bind' || item.kind === 'include');
632
957
  const startup = this.file.items.filter(item => item.kind !== 'import' && item.kind !== 'export' && item.kind !== 'test' && item.kind !== 'bind' && item.kind !== 'include' && !declarations.includes(item));
633
- // Render behavior first to discover the exact dependency surface, including wildcard imports.
634
- const behavior = (exports.length ? '## Folder exports\n\n' + exports.map(item => this.declaration(item)).join('') + '\n' : '') +
635
- (providers.length ? '## Dependency providers\n\nRegister these providers before startup. Their declaration order does not set initialization order; shared instances initialize in dependency order.\n\n' + providers.map(item => this.declaration(item)).join('') + '\n' : '') +
636
- (startup.length ? '## Startup, in source order\n\n' + startup.map(item => this.declaration(item)).join('') + '\n' : '') +
637
- declarations.sort((a, b) => Number('name' in a && a.name.startsWith('_')) - Number('name' in b && b.name.startsWith('_'))).map(item => this.declaration(item)).join('') +
638
- this.file.items.filter(item => item.kind === 'test').map(item => this.declaration(item)).join('');
639
- const surface = this.dependencySurface() + this.builtinSurface();
640
- let config = '';
958
+ const children = [];
959
+ if (declarations.some(item => item.kind === 'function' && item.endpoint))
960
+ children.push(paragraph('Plain handler results default to HTTP 200 unless another status is declared. HttpResponse values choose their own status. Unhandled request failures return HTTP 500 and cancel the request tasks.'));
641
961
  if (basename(this.file.path) === 'main.aug') {
642
962
  const project = this.checked.project;
643
963
  if (project.files.get(this.file.path)?.items.some(item => item.kind === 'serve'))
644
- config += '## HTTP configuration\n\n' +
645
- `Listen on ${code(project.config.web.host)}. Limit request bodies to ${project.config.web.body_limit} bytes and buffered responses to ${project.config.web.response_limit} bytes.\n\n` +
646
- (project.config.web.tls.certificate ? 'Use TLS with the configured certificate and private key.\n\n' : '') +
647
- (project.config.web.http3 ? 'Enable HTTP/3 over TLS.\n\n' : '') +
648
- (project.config.openapi.enabled ? `Serve the OpenAPI document at ${code(project.config.openapi.path)} and API documentation at ${code(project.config.openapi.docs)}.\n\n` : '');
649
- }
650
- return generated + '\n\n# ' + code(basename(this.file.path)) + ' specification\n\n' +
651
- `August ${compilerVersion()}. This document is compiled from checked code with deterministic wording guided by Simplified Technical English.\n\n` +
652
- this.fileOverview() + config + behavior + surface +
653
- (behavior ? '' : 'This file declares no operations.\n') +
654
- '## Shared language rules\n\nSee the [language reference](https://greenpandastudios.github.io/augscript/reference) for numeric, equality, ownership, and task rules.\n';
964
+ children.push(section('HTTP configuration', 2, [paragraph(`Listen on ${code(project.config.web.host)}. Limit request bodies to ${project.config.web.body_limit} bytes and buffered responses to ${project.config.web.response_limit} bytes. ` +
965
+ (project.config.web.tls.certificate ? 'Use TLS. ' : '') +
966
+ (project.config.web.http3 ? 'Enable HTTP/3. ' : '') +
967
+ (project.config.openapi.enabled ? `Serve OpenAPI at ${code(project.config.openapi.path)} and API docs at ${code(project.config.openapi.docs)}. ` : ''))]));
968
+ }
969
+ // Build the checked explanation first; rendering decides all spacing.
970
+ if (exports.length)
971
+ children.push(section('Exports', 2, exports.map(item => paragraph(this.exportLine(item)))));
972
+ if (providers.length)
973
+ children.push(section('Providers', 2, providers.map(item => paragraph(this.providerLine(item)))));
974
+ if (startup.length)
975
+ children.push(section('Startup', 2, [flow(startup.flatMap(item => this.statement(item)))]));
976
+ children.push(...declarations.sort((a, b) => Number('name' in a && a.name.startsWith('_')) - Number('name' in b && b.name.startsWith('_'))).map(item => this.declaration(item)));
977
+ children.push(...this.file.items.filter(item => item.kind === 'test').map(item => this.declaration(item)));
978
+ if (!children.length)
979
+ children.push(paragraph('This file declares no operations.'));
980
+ // Links may add inherited operation owners. Walk until the surface is closed.
981
+ const surfaceSize = () => [...this.used.values()].reduce((size, entry) => size + 1 + entry.operations.size + entry.fields.size + Number(entry.constructed ?? false), 0);
982
+ let surface, size = -1;
983
+ do {
984
+ size = surfaceSize();
985
+ surface = this.dependencySurface();
986
+ } while (surfaceSize() !== size);
987
+ if (surface)
988
+ children.push(surface);
989
+ const builtins = this.builtinSurface();
990
+ if (builtins)
991
+ children.push(builtins);
992
+ return generated + '\n\n' + renderSpecTree(section(code(basename(this.file.path)), 1, children));
655
993
  }
656
994
  builtinSurface() {
657
995
  if (!this.builtins.size && !this.properties.size)
658
- return '';
659
- return '## Built-in operations used by this file\n\n' + [...this.properties].sort(([a], [b]) => compare(a, b)).map(([name, property]) => `- ${code(name)} (${code(property.type)}): ${property.documentation}\n`).join('') +
660
- [...this.builtins].sort(([a], [b]) => compare(a, b)).map(([name, operation]) => `- ${code(name)} (${operation.parameters.map(param => code(param.label) + ': ' + code(param.type)).join(', ') || 'no inputs'}) → ${code(operation.returns)}: ${operation.documentation}` +
661
- (operation.changes ? ' Changes the receiver.' : '') +
662
- (operation.errors?.length ? ' Can fail with ' + operation.errors.map(code).join(', ') + '.' : '') + '\n').join('') +
663
- '\n[Full built-in reference](https://greenpandastudios.github.io/augscript/language-constructs).\n\n';
996
+ return;
997
+ return paragraph('Built-in operations follow the [language reference](https://greenpandastudios.github.io/augscript/language-constructs).');
664
998
  }
665
999
  }
666
- /** Check generated artifacts without writing; protect neighboring handwritten documents. */
1000
+ /** Refresh pointers and artifacts atomically, or check drift without writing; protect handwritten documents. */
667
1001
  export function updateSpecs(checked, check = false, options = {}) {
668
1002
  const outputs = generateSpecs(checked, options), root = checked.project.root;
669
1003
  const manifest = join(root, '.aug-spec', 'manifest.json');
670
1004
  let previous = [];
1005
+ const previousDescriptors = new Map();
1006
+ const descriptorPath = (path) => /^\.aug-spec\/packages\/(?:@[^/]+\/)?[^/]+\/[^/]+\/native\.abi\.json$/.test(path) && !path.includes('\\') && !path.split('/').some(part => part === '.' || part === '..');
671
1007
  if (options.manifest !== false && existsSync(manifest)) {
672
1008
  const saved = JSON.parse(readFileSync(manifest, 'utf8'));
673
- if (saved.format !== 1 || !Array.isArray(saved.files) || saved.files.some(path => typeof path !== 'string' || relative(root, resolve(root, path)).startsWith('..') || !path.endsWith('.aug') && !path.endsWith('.aug.md')))
1009
+ if (saved.format !== 1 || !Array.isArray(saved.files) || saved.files.some(path => typeof path !== 'string' || relative(root, resolve(root, path)).startsWith('..') || !path.endsWith('.aug') && !path.endsWith('.aug.md') && !descriptorPath(path)))
674
1010
  throw new Error('Invalid generated specification manifest');
675
1011
  previous = saved.files.map(path => resolve(root, path));
1012
+ for (const [path, digest] of Object.entries(saved.nativeDescriptors ?? {})) {
1013
+ if (!descriptorPath(path) || !saved.files.includes(path) || typeof digest !== 'string' || !/^[0-9a-f]{64}$/.test(digest))
1014
+ throw new Error('Invalid generated native descriptor manifest');
1015
+ previousDescriptors.set(resolve(root, path), digest);
1016
+ }
676
1017
  }
677
- const names = new Set(outputs.map(output => output.path));
1018
+ const artifacts = outputs.filter(output => output.kind !== 'source-hint');
1019
+ const names = new Set(artifacts.map(output => output.path));
678
1020
  const removed = previous.filter(path => !names.has(path) && existsSync(path));
679
1021
  const stale = outputs.filter(output => !existsSync(output.path) || readFileSync(output.path, 'utf8') !== output.text).map(output => relative(root, output.path));
680
1022
  stale.push(...removed.map(path => relative(root, path)));
681
- const manifestText = JSON.stringify({ format: 1, files: outputs.map(output => relative(root, output.path).replaceAll('\\', '/')), digest: hash(outputs.map(output => output.text).join('\0')) }, null, 2) + '\n';
1023
+ const descriptors = artifacts.filter(output => output.kind === 'native-descriptor');
1024
+ const manifestText = JSON.stringify({ format: 1, files: artifacts.map(output => relative(root, output.path).replaceAll('\\', '/')),
1025
+ ...(descriptors.length ? { nativeDescriptors: Object.fromEntries(descriptors.map(output => [relative(root, output.path).replaceAll('\\', '/'), hash(output.text)])) } : {}),
1026
+ digest: hash(artifacts.map(output => output.text).join('\0')) }, null, 2) + '\n';
682
1027
  if (options.manifest !== false && (!existsSync(manifest) || readFileSync(manifest, 'utf8') !== manifestText))
683
1028
  stale.push('.aug-spec/manifest.json');
684
1029
  if (check)
@@ -697,39 +1042,58 @@ export function updateSpecs(checked, check = false, options = {}) {
697
1042
  if (lstatSync(output.path).isSymbolicLink())
698
1043
  throw new Error(`Specification output is a symbolic link: ${output.path}`);
699
1044
  const text = readFileSync(output.path, 'utf8');
700
- if (!text.startsWith(generated) && !text.startsWith(copied))
1045
+ if (output.kind === 'source-hint') {
1046
+ if (text !== checked.project.files.get(output.source)?.source)
1047
+ throw new Error('Source changed during compilation; retry before generating specifications');
1048
+ }
1049
+ else if (output.kind === 'native-descriptor') {
1050
+ // An unchanged descriptor from an earlier preview may be adopted. A
1051
+ // tracked copy must still match its recorded bytes before replacement.
1052
+ const tracked = previousDescriptors.get(output.path);
1053
+ if (tracked ? hash(text) !== tracked : text !== output.text)
1054
+ throw new Error(`Refusing to overwrite edited native descriptor ${output.path}`);
1055
+ }
1056
+ else if (!text.startsWith(generated) && !text.startsWith(copied))
701
1057
  throw new Error(`Refusing to overwrite handwritten file ${output.path}`);
702
1058
  }
703
1059
  }
1060
+ for (const path of removed)
1061
+ if (previousDescriptors.has(path) &&
1062
+ (lstatSync(path).isSymbolicLink() || hash(readFileSync(path, 'utf8')) !== previousDescriptors.get(path)))
1063
+ throw new Error(`Refusing to remove edited native descriptor ${path}`);
704
1064
  for (const output of outputs) {
705
1065
  if (existsSync(output.path) && readFileSync(output.path, 'utf8') === output.text)
706
1066
  continue;
707
1067
  mkdirSync(dirname(output.path), { recursive: true });
708
1068
  const temporary = output.path + '.aug-spec-tmp';
1069
+ let created = false;
709
1070
  try {
710
- writeFileSync(temporary, output.text, { flag: 'wx' });
1071
+ writeFileSync(temporary, output.text, { flag: 'wx', mode: output.kind === 'source-hint' ? lstatSync(output.path).mode : undefined });
1072
+ created = true;
711
1073
  renameSync(temporary, output.path);
712
1074
  }
713
1075
  finally {
714
- if (existsSync(temporary))
1076
+ if (created && existsSync(temporary))
715
1077
  rmSync(temporary);
716
1078
  }
717
1079
  }
718
1080
  for (const path of removed) {
719
1081
  const text = readFileSync(path, 'utf8');
720
- if (text.startsWith(generated) || text.startsWith(copied))
1082
+ if (previousDescriptors.has(path) || text.startsWith(generated) || text.startsWith(copied))
721
1083
  rmSync(path);
722
1084
  }
723
1085
  if (options.manifest !== false) {
724
1086
  mkdirSync(dirname(manifest), { recursive: true });
725
1087
  if (!existsSync(manifest) || readFileSync(manifest, 'utf8') !== manifestText) {
726
1088
  const temporary = manifest + '.aug-spec-tmp';
1089
+ let created = false;
727
1090
  try {
728
1091
  writeFileSync(temporary, manifestText, { flag: 'wx' });
1092
+ created = true;
729
1093
  renameSync(temporary, manifest);
730
1094
  }
731
1095
  finally {
732
- if (existsSync(temporary))
1096
+ if (created && existsSync(temporary))
733
1097
  rmSync(temporary);
734
1098
  }
735
1099
  }