@astrale-os/sdk 0.4.11 → 0.4.13

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 (331) hide show
  1. package/dist/auth/authenticate.d.ts +1 -1
  2. package/dist/auth/authenticate.d.ts.map +1 -1
  3. package/dist/auth/authenticate.js +2 -2
  4. package/dist/auth/authenticate.js.map +1 -1
  5. package/dist/auth/check.js +1 -1
  6. package/dist/auth/check.js.map +1 -1
  7. package/dist/auth/function-context.d.ts +1 -1
  8. package/dist/auth/function-context.d.ts.map +1 -1
  9. package/dist/auth/function-context.js +3 -3
  10. package/dist/auth/function-context.js.map +1 -1
  11. package/dist/auth/index.d.ts +15 -15
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +10 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/issuer-mint.d.ts +1 -1
  16. package/dist/auth/issuer-mint.d.ts.map +1 -1
  17. package/dist/auth/issuer-mint.js +2 -2
  18. package/dist/auth/issuer-mint.js.map +1 -1
  19. package/dist/auth/kernel-client.d.ts +1 -1
  20. package/dist/auth/kernel-client.d.ts.map +1 -1
  21. package/dist/auth/kernel-client.js +2 -2
  22. package/dist/auth/kernel-client.js.map +1 -1
  23. package/dist/auth/resolve.d.ts +2 -2
  24. package/dist/auth/resolve.d.ts.map +1 -1
  25. package/dist/auth/resolve.js +2 -2
  26. package/dist/auth/resolve.js.map +1 -1
  27. package/dist/auth/verify.d.ts +1 -1
  28. package/dist/auth/verify.d.ts.map +1 -1
  29. package/dist/auth/verify.js +2 -2
  30. package/dist/auth/verify.js.map +1 -1
  31. package/dist/cli/bin.js +1 -1
  32. package/dist/cli/bin.js.map +1 -1
  33. package/dist/cli/index.d.ts +2 -2
  34. package/dist/cli/index.d.ts.map +1 -1
  35. package/dist/cli/index.js +2 -2
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/publish.js +1 -1
  38. package/dist/cli/publish.js.map +1 -1
  39. package/dist/cli/run.d.ts +9 -3
  40. package/dist/cli/run.d.ts.map +1 -1
  41. package/dist/cli/run.js +55 -5
  42. package/dist/cli/run.js.map +1 -1
  43. package/dist/cli/spec.d.ts +1 -1
  44. package/dist/cli/spec.d.ts.map +1 -1
  45. package/dist/cli/spec.js +1 -1
  46. package/dist/cli/spec.js.map +1 -1
  47. package/dist/config/define-domain.d.ts +2 -2
  48. package/dist/config/define-domain.d.ts.map +1 -1
  49. package/dist/config/deploy.d.ts +2 -2
  50. package/dist/config/deploy.d.ts.map +1 -1
  51. package/dist/config/index.d.ts +6 -6
  52. package/dist/config/index.d.ts.map +1 -1
  53. package/dist/config/index.js +3 -3
  54. package/dist/config/index.js.map +1 -1
  55. package/dist/define/index.d.ts +4 -4
  56. package/dist/define/index.d.ts.map +1 -1
  57. package/dist/define/index.js +2 -2
  58. package/dist/define/index.js.map +1 -1
  59. package/dist/define/remote-function.d.ts +7 -5
  60. package/dist/define/remote-function.d.ts.map +1 -1
  61. package/dist/define/remote-function.js.map +1 -1
  62. package/dist/define/view.d.ts +3 -3
  63. package/dist/define/view.d.ts.map +1 -1
  64. package/dist/deploy/check.d.ts +1 -1
  65. package/dist/deploy/check.d.ts.map +1 -1
  66. package/dist/deploy/check.js +1 -1
  67. package/dist/deploy/check.js.map +1 -1
  68. package/dist/deploy/index.d.ts +2 -2
  69. package/dist/deploy/index.d.ts.map +1 -1
  70. package/dist/deploy/index.js +2 -2
  71. package/dist/deploy/index.js.map +1 -1
  72. package/dist/dispatch/authorize.js +1 -1
  73. package/dist/dispatch/authorize.js.map +1 -1
  74. package/dist/dispatch/dispatcher.d.ts +11 -21
  75. package/dist/dispatch/dispatcher.d.ts.map +1 -1
  76. package/dist/dispatch/dispatcher.js +66 -65
  77. package/dist/dispatch/dispatcher.js.map +1 -1
  78. package/dist/dispatch/execute.d.ts +7 -6
  79. package/dist/dispatch/execute.d.ts.map +1 -1
  80. package/dist/dispatch/execute.js +1 -0
  81. package/dist/dispatch/execute.js.map +1 -1
  82. package/dist/dispatch/identity.d.ts +2 -2
  83. package/dist/dispatch/identity.d.ts.map +1 -1
  84. package/dist/dispatch/index.d.ts +6 -6
  85. package/dist/dispatch/index.d.ts.map +1 -1
  86. package/dist/dispatch/index.js +6 -6
  87. package/dist/dispatch/index.js.map +1 -1
  88. package/dist/dispatch/resolve.d.ts +1 -1
  89. package/dist/dispatch/resolve.d.ts.map +1 -1
  90. package/dist/dispatch/self.d.ts +1 -1
  91. package/dist/dispatch/self.d.ts.map +1 -1
  92. package/dist/domain/build-spec.d.ts +1 -1
  93. package/dist/domain/build-spec.d.ts.map +1 -1
  94. package/dist/domain/build-spec.js +2 -2
  95. package/dist/domain/build-spec.js.map +1 -1
  96. package/dist/domain/contract.d.ts +1 -1
  97. package/dist/domain/contract.d.ts.map +1 -1
  98. package/dist/domain/define.d.ts +4 -4
  99. package/dist/domain/define.d.ts.map +1 -1
  100. package/dist/domain/define.js +2 -2
  101. package/dist/domain/define.js.map +1 -1
  102. package/dist/domain/extend-functions.d.ts +1 -1
  103. package/dist/domain/extend-functions.d.ts.map +1 -1
  104. package/dist/domain/extend-functions.js +1 -1
  105. package/dist/domain/extend-functions.js.map +1 -1
  106. package/dist/domain/extend-views.d.ts +1 -1
  107. package/dist/domain/extend-views.d.ts.map +1 -1
  108. package/dist/domain/extend-views.js +1 -1
  109. package/dist/domain/extend-views.js.map +1 -1
  110. package/dist/domain/index.d.ts +4 -4
  111. package/dist/domain/index.d.ts.map +1 -1
  112. package/dist/domain/index.js +3 -3
  113. package/dist/domain/index.js.map +1 -1
  114. package/dist/index.d.ts +19 -19
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +10 -10
  117. package/dist/index.js.map +1 -1
  118. package/dist/linter/analyze.d.ts +6 -0
  119. package/dist/linter/analyze.d.ts.map +1 -0
  120. package/dist/linter/analyze.js +12 -0
  121. package/dist/linter/analyze.js.map +1 -0
  122. package/dist/linter/diagnostic.d.ts +32 -0
  123. package/dist/linter/diagnostic.d.ts.map +1 -0
  124. package/dist/linter/diagnostic.js +35 -0
  125. package/dist/linter/diagnostic.js.map +1 -0
  126. package/dist/linter/index.d.ts +10 -0
  127. package/dist/linter/index.d.ts.map +1 -0
  128. package/dist/linter/index.js +6 -0
  129. package/dist/linter/index.js.map +1 -0
  130. package/dist/linter/lint.d.ts +8 -0
  131. package/dist/linter/lint.d.ts.map +1 -0
  132. package/dist/linter/lint.js +26 -0
  133. package/dist/linter/lint.js.map +1 -0
  134. package/dist/linter/oxlint/config.d.ts +14 -0
  135. package/dist/linter/oxlint/config.d.ts.map +1 -0
  136. package/dist/linter/oxlint/config.js +18 -0
  137. package/dist/linter/oxlint/config.js.map +1 -0
  138. package/dist/linter/oxlint/plugin.d.ts +13 -0
  139. package/dist/linter/oxlint/plugin.d.ts.map +1 -0
  140. package/dist/linter/oxlint/plugin.js +14 -0
  141. package/dist/linter/oxlint/plugin.js.map +1 -0
  142. package/dist/linter/oxlint/run.d.ts +7 -0
  143. package/dist/linter/oxlint/run.d.ts.map +1 -0
  144. package/dist/linter/oxlint/run.js +122 -0
  145. package/dist/linter/oxlint/run.js.map +1 -0
  146. package/dist/linter/preflight.d.ts +2 -0
  147. package/dist/linter/preflight.d.ts.map +1 -0
  148. package/dist/linter/preflight.js +162 -0
  149. package/dist/linter/preflight.js.map +1 -0
  150. package/dist/linter/project.d.ts +12 -0
  151. package/dist/linter/project.d.ts.map +1 -0
  152. package/dist/linter/project.js +88 -0
  153. package/dist/linter/project.js.map +1 -0
  154. package/dist/linter/report.d.ts +4 -0
  155. package/dist/linter/report.d.ts.map +1 -0
  156. package/dist/linter/report.js +22 -0
  157. package/dist/linter/report.js.map +1 -0
  158. package/dist/linter/rules/catalog.d.ts +56 -0
  159. package/dist/linter/rules/catalog.d.ts.map +1 -0
  160. package/dist/linter/rules/catalog.js +53 -0
  161. package/dist/linter/rules/catalog.js.map +1 -0
  162. package/dist/linter/rules/core-has-no-async.d.ts +3 -0
  163. package/dist/linter/rules/core-has-no-async.d.ts.map +1 -0
  164. package/dist/linter/rules/core-has-no-async.js +43 -0
  165. package/dist/linter/rules/core-has-no-async.js.map +1 -0
  166. package/dist/linter/rules/core-is-pure.d.ts +3 -0
  167. package/dist/linter/rules/core-is-pure.d.ts.map +1 -0
  168. package/dist/linter/rules/core-is-pure.js +74 -0
  169. package/dist/linter/rules/core-is-pure.js.map +1 -0
  170. package/dist/linter/rules/no-nested-steps.d.ts +3 -0
  171. package/dist/linter/rules/no-nested-steps.d.ts.map +1 -0
  172. package/dist/linter/rules/no-nested-steps.js +32 -0
  173. package/dist/linter/rules/no-nested-steps.js.map +1 -0
  174. package/dist/linter/rules/no-read-inside-mutate.d.ts +3 -0
  175. package/dist/linter/rules/no-read-inside-mutate.d.ts.map +1 -0
  176. package/dist/linter/rules/no-read-inside-mutate.js +64 -0
  177. package/dist/linter/rules/no-read-inside-mutate.js.map +1 -0
  178. package/dist/linter/rules/one-remote-definition-per-file.d.ts +3 -0
  179. package/dist/linter/rules/one-remote-definition-per-file.d.ts.map +1 -0
  180. package/dist/linter/rules/one-remote-definition-per-file.js +23 -0
  181. package/dist/linter/rules/one-remote-definition-per-file.js.map +1 -0
  182. package/dist/linter/rules/oxlint-ast.d.ts +30 -0
  183. package/dist/linter/rules/oxlint-ast.d.ts.map +1 -0
  184. package/dist/linter/rules/oxlint-ast.js +104 -0
  185. package/dist/linter/rules/oxlint-ast.js.map +1 -0
  186. package/dist/linter/rules/oxlint-rule.d.ts +45 -0
  187. package/dist/linter/rules/oxlint-rule.d.ts.map +1 -0
  188. package/dist/linter/rules/oxlint-rule.js +2 -0
  189. package/dist/linter/rules/oxlint-rule.js.map +1 -0
  190. package/dist/linter/rules/project-rule.d.ts +10 -0
  191. package/dist/linter/rules/project-rule.d.ts.map +1 -0
  192. package/dist/linter/rules/project-rule.js +24 -0
  193. package/dist/linter/rules/project-rule.js.map +1 -0
  194. package/dist/linter/rules/thin-schema-composition-root.d.ts +3 -0
  195. package/dist/linter/rules/thin-schema-composition-root.d.ts.map +1 -0
  196. package/dist/linter/rules/thin-schema-composition-root.js +23 -0
  197. package/dist/linter/rules/thin-schema-composition-root.js.map +1 -0
  198. package/dist/linter/source.d.ts +24 -0
  199. package/dist/linter/source.d.ts.map +1 -0
  200. package/dist/linter/source.js +279 -0
  201. package/dist/linter/source.js.map +1 -0
  202. package/dist/linter/suppression.d.ts +3 -0
  203. package/dist/linter/suppression.d.ts.map +1 -0
  204. package/dist/linter/suppression.js +11 -0
  205. package/dist/linter/suppression.js.map +1 -0
  206. package/dist/method/class.d.ts +3 -3
  207. package/dist/method/class.d.ts.map +1 -1
  208. package/dist/method/context.d.ts +6 -4
  209. package/dist/method/context.d.ts.map +1 -1
  210. package/dist/method/index.d.ts +5 -5
  211. package/dist/method/index.d.ts.map +1 -1
  212. package/dist/method/index.js +2 -2
  213. package/dist/method/index.js.map +1 -1
  214. package/dist/method/single.d.ts +2 -2
  215. package/dist/method/single.d.ts.map +1 -1
  216. package/dist/server/auxiliary-routes.d.ts +3 -3
  217. package/dist/server/auxiliary-routes.d.ts.map +1 -1
  218. package/dist/server/auxiliary-routes.js +11 -11
  219. package/dist/server/auxiliary-routes.js.map +1 -1
  220. package/dist/server/config.d.ts +1 -1
  221. package/dist/server/config.d.ts.map +1 -1
  222. package/dist/server/create.d.ts +2 -2
  223. package/dist/server/create.d.ts.map +1 -1
  224. package/dist/server/create.js +11 -11
  225. package/dist/server/create.js.map +1 -1
  226. package/dist/server/domain-entry.d.ts +5 -5
  227. package/dist/server/domain-entry.d.ts.map +1 -1
  228. package/dist/server/domain-entry.js +2 -2
  229. package/dist/server/domain-entry.js.map +1 -1
  230. package/dist/server/index.d.ts +11 -11
  231. package/dist/server/index.d.ts.map +1 -1
  232. package/dist/server/index.js +7 -7
  233. package/dist/server/index.js.map +1 -1
  234. package/dist/server/start.d.ts +1 -1
  235. package/dist/server/start.d.ts.map +1 -1
  236. package/dist/server/start.js.map +1 -1
  237. package/dist/server/worker-entry.d.ts +1 -1
  238. package/dist/server/worker-entry.d.ts.map +1 -1
  239. package/dist/server/worker-entry.js +3 -3
  240. package/dist/server/worker-entry.js.map +1 -1
  241. package/dist/step/index.d.ts +4 -4
  242. package/dist/step/index.d.ts.map +1 -1
  243. package/dist/step/index.js +2 -2
  244. package/dist/step/index.js.map +1 -1
  245. package/dist/step/inline.d.ts +1 -1
  246. package/dist/step/inline.d.ts.map +1 -1
  247. package/dist/step/inline.js +1 -1
  248. package/dist/step/inline.js.map +1 -1
  249. package/package.json +29 -8
  250. package/src/auth/authenticate.ts +3 -3
  251. package/src/auth/check.ts +1 -1
  252. package/src/auth/function-context.ts +4 -4
  253. package/src/auth/index.ts +15 -15
  254. package/src/auth/issuer-mint.ts +3 -3
  255. package/src/auth/kernel-client.ts +3 -3
  256. package/src/auth/resolve.ts +4 -4
  257. package/src/auth/verify.ts +3 -3
  258. package/src/cli/bin.ts +1 -1
  259. package/src/cli/index.ts +2 -2
  260. package/src/cli/publish.ts +1 -1
  261. package/src/cli/run.ts +59 -8
  262. package/src/cli/spec.ts +3 -3
  263. package/src/config/define-domain.ts +2 -2
  264. package/src/config/deploy.ts +2 -2
  265. package/src/config/index.ts +6 -6
  266. package/src/define/index.ts +4 -4
  267. package/src/define/remote-function.ts +7 -5
  268. package/src/define/view.ts +3 -3
  269. package/src/deploy/check.ts +2 -2
  270. package/src/deploy/index.ts +2 -2
  271. package/src/dispatch/authorize.ts +1 -1
  272. package/src/dispatch/dispatcher.ts +76 -83
  273. package/src/dispatch/execute.ts +8 -6
  274. package/src/dispatch/identity.ts +2 -2
  275. package/src/dispatch/index.ts +6 -6
  276. package/src/dispatch/resolve.ts +1 -1
  277. package/src/dispatch/self.ts +1 -1
  278. package/src/domain/build-spec.ts +4 -4
  279. package/src/domain/contract.ts +1 -1
  280. package/src/domain/define.ts +6 -6
  281. package/src/domain/extend-functions.ts +2 -2
  282. package/src/domain/extend-views.ts +2 -2
  283. package/src/domain/index.ts +4 -4
  284. package/src/index.ts +20 -20
  285. package/src/linter/analyze.ts +17 -0
  286. package/src/linter/diagnostic.ts +68 -0
  287. package/src/linter/docs/DIAGNOSTICS.md +47 -0
  288. package/src/linter/docs/JUDGMENT.md +81 -0
  289. package/src/linter/docs/PRINCIPLES.md +97 -0
  290. package/src/linter/docs/README.md +38 -0
  291. package/src/linter/docs/RULE-AUTHORING.md +144 -0
  292. package/src/linter/docs/RULES.md +153 -0
  293. package/src/linter/index.ts +14 -0
  294. package/src/linter/lint.ts +32 -0
  295. package/src/linter/oxlint/astrale-ox.d.ts +5 -0
  296. package/src/linter/oxlint/config.ts +19 -0
  297. package/src/linter/oxlint/plugin.js +16 -0
  298. package/src/linter/oxlint/run.ts +162 -0
  299. package/src/linter/preflight.ts +181 -0
  300. package/src/linter/project.ts +108 -0
  301. package/src/linter/report.ts +25 -0
  302. package/src/linter/rules/catalog.ts +67 -0
  303. package/src/linter/rules/core-has-no-async.js +43 -0
  304. package/src/linter/rules/core-is-pure.ts +103 -0
  305. package/src/linter/rules/no-nested-steps.js +37 -0
  306. package/src/linter/rules/no-read-inside-mutate.js +72 -0
  307. package/src/linter/rules/one-remote-definition-per-file.ts +29 -0
  308. package/src/linter/rules/oxlint-ast.js +110 -0
  309. package/src/linter/rules/oxlint-rule.ts +35 -0
  310. package/src/linter/rules/project-rule.ts +37 -0
  311. package/src/linter/rules/thin-schema-composition-root.ts +26 -0
  312. package/src/linter/source.ts +323 -0
  313. package/src/linter/suppression.ts +11 -0
  314. package/src/method/class.ts +3 -3
  315. package/src/method/context.ts +6 -4
  316. package/src/method/index.ts +5 -5
  317. package/src/method/single.ts +2 -2
  318. package/src/server/auxiliary-routes.ts +22 -17
  319. package/src/server/config.ts +1 -1
  320. package/src/server/create.ts +15 -15
  321. package/src/server/domain-entry.ts +7 -7
  322. package/src/server/index.ts +11 -11
  323. package/src/server/start.ts +3 -2
  324. package/src/server/worker-entry.ts +4 -4
  325. package/src/step/index.ts +4 -4
  326. package/src/step/inline.ts +2 -2
  327. package/dist/defer/index.d.ts +0 -24
  328. package/dist/defer/index.d.ts.map +0 -1
  329. package/dist/defer/index.js +0 -42
  330. package/dist/defer/index.js.map +0 -1
  331. package/src/defer/index.ts +0 -64
@@ -0,0 +1,144 @@
1
+ # Rule Authoring
2
+
3
+ ## Identity
4
+
5
+ Every Astrale-specific domain rule has one canonical semantic identifier:
6
+
7
+ ```text
8
+ astrale/<slug>
9
+ ```
10
+
11
+ Examples:
12
+
13
+ ```text
14
+ astrale/core-is-pure
15
+ astrale/effects-run-in-step
16
+ astrale/no-read-inside-mutate
17
+ ```
18
+
19
+ The `astrale` namespace is declared once by the linter. The registry stores only the slug. Slugs use
20
+ lower-case kebab case, state the invariant clearly, and avoid engine words such as `oxlint`, `ast`, or
21
+ `typescript`.
22
+
23
+ Prefer established lint vocabulary:
24
+
25
+ - `no-*` prohibits a concrete pattern.
26
+ - `require-*` requires explicit syntax or evidence.
27
+ - `prefer-*` is advisory when valid exceptions exist.
28
+ - A direct invariant such as `core-is-pure` is acceptable when it reads more clearly.
29
+
30
+ Artificial abbreviations, trigrams, category prefixes, and monotonic numbers are not identifiers.
31
+ Family and owner can change without renaming a rule.
32
+
33
+ ## Stability
34
+
35
+ Identifier stability begins at `accepted`, not `proposed`:
36
+
37
+ - A `proposed` rule may be renamed, merged, or removed freely.
38
+ - An `accepted` or later rule keeps its canonical identifier.
39
+ - If an accepted rule must be renamed, its old identifier remains a deprecated alias through at least
40
+ the next major SDK release.
41
+ - A retired identifier is never assigned to a different invariant.
42
+
43
+ This preserves early design freedom without maintaining an opaque surrogate code alongside every
44
+ readable rule name.
45
+
46
+ ## Classification
47
+
48
+ Every registry entry declares:
49
+
50
+ | Field | Values |
51
+ | --- | --- |
52
+ | `family` | `structure`, `purity`, `schema`, `authorization`, `durability`, `graph`, `errors`, or `client` |
53
+ | `owner` | `sdk`, `oxlint`, or `analyzer` |
54
+ | `scope` | `node`, `file`, or `project` |
55
+ | `automation` | `implement`, `research`, or `guidance` |
56
+ | `default` | `error`, `warning`, or `off` |
57
+ | `fix` | `safe`, `suggestion`, or `none` |
58
+ | `status` | `proposed`, `accepted`, `implemented`, `enforced`, `deprecated`, or `retired` |
59
+
60
+ Classification is metadata, not identity. Moving `astrale/authorize-is-explicit` from an analyzer hint
61
+ to an SDK type invariant preserves its identifier, suppressions, documentation URL, and history.
62
+
63
+ `automation` is an explicit decision, not a maturity score:
64
+
65
+ - `implement` means the current contract is suitable for automation and may enter the rule lifecycle.
66
+ - `research` means the idea is not approved for implementation. Its row must state what evidence or
67
+ narrower contract is missing.
68
+ - `guidance` means the current formulation should remain knowledge or review guidance. Its row remains
69
+ in the registry as a decision record, with `default: off` and no fix.
70
+
71
+ Changing `research` or `guidance` to `implement` requires a review of the detection contract and
72
+ false-positive fixtures. It is never implied merely because an engine could approximate the rule.
73
+
74
+ ## Minimal policy specification
75
+
76
+ Every entry in [RULES.md](./RULES.md) records:
77
+
78
+ - A unique semantic slug.
79
+ - Family, primary owner, scope, automation disposition, intended default severity, fix class, and
80
+ status.
81
+ - One-sentence invariant.
82
+ - For `research`, the evidence or narrower contract needed before implementation.
83
+ - For `guidance`, why static enforcement would be misleading.
84
+
85
+ Before an `implement` entry can become `accepted`, it additionally requires:
86
+
87
+ - A deterministic detection contract and relevant source locations.
88
+ - At least one valid and invalid fixture.
89
+ - Representative legitimate alternatives and documented exceptions that must not trigger.
90
+
91
+ An accepted rule should graduate to a dedicated document only when examples, options, or edge cases no
92
+ longer fit clearly in the registry. Its filename is `<slug>.md`, for example
93
+ `effects-run-in-step.md`.
94
+
95
+ ## Lifecycle
96
+
97
+ ```text
98
+ proposed -> accepted -> implemented -> enforced
99
+ | |
100
+ +-> deprecated +-> deprecated -> retired
101
+ ```
102
+
103
+ - `proposed`: candidate policy; no compatibility guarantee and no default diagnostic.
104
+ - `accepted`: identity, detection contract, and ownership approved.
105
+ - `implemented`: rule exists behind an explicit experimental preset.
106
+ - `enforced`: enabled at its documented severity in the strict preset.
107
+ - `deprecated`: still recognized, with replacement guidance where applicable.
108
+ - `retired`: no diagnostic is emitted; its historical identifier remains reserved.
109
+
110
+ No rule moves directly from prose to `enforced`.
111
+
112
+ Only entries classified `implement` may move from `proposed` to `accepted`. A `research` entry may have
113
+ an isolated prototype for measurement, but that prototype must not emit default project diagnostics.
114
+ A `guidance` entry does not enter the executable lifecycle unless it is narrowed and reclassified.
115
+
116
+ ## Severity policy
117
+
118
+ - `error` means the code is structurally unsafe, invalid, or violates a settled Astrale contract.
119
+ - `warning` means the observation is reliable but the observed pattern is risky rather than invalid in
120
+ every occurrence.
121
+ - `off` means the rule is experimental, expensive, or awaiting platform support.
122
+
123
+ Severity is not a proxy for implementation progress. An unimplemented rule remains `proposed` even if
124
+ its intended default is `error`.
125
+
126
+ Warnings are not a shelter for noisy heuristics. A likely false positive still consumes attention,
127
+ creates suppressions, and weakens trust in every other diagnostic.
128
+
129
+ ## Owner migration
130
+
131
+ Enforcement moves to the earliest reliable owner. A source rule should migrate from Oxlint or the
132
+ analyzer to `sdk` once an SDK type, schema compilation path, or runtime authoring contract can make the
133
+ invalid shape impossible. During migration, one owner emits the diagnostic; dual reporting is a bug.
134
+
135
+ ## Rule test contract
136
+
137
+ Every implemented rule includes:
138
+
139
+ - Minimal valid fixtures.
140
+ - Minimal invalid fixtures with exact identifier, location, and message.
141
+ - False-positive fixtures for documented exceptions.
142
+ - Suppression fixtures.
143
+ - Fix snapshots when `fix` is `safe` or `suggestion`.
144
+ - A performance fixture when the rule walks imports, types, or compiled schema.
@@ -0,0 +1,153 @@
1
+ # Rule Registry
2
+
3
+ This is the canonical registry of Astrale-specific domain-policy candidates. It intentionally keeps
4
+ both executable candidates and ideas that should remain guidance, so rejected automation ideas are not
5
+ rediscovered later. The executable-status table records what is currently enforced; every other entry
6
+ remains `proposed` regardless of its automation disposition.
7
+
8
+ The canonical identifier is `astrale/<slug>`. Each section heading supplies the `family`; owner, scope,
9
+ automation disposition, intended default, and fix class are explicit in every row. `owner` identifies
10
+ the prospective evidence owner; it does not imply an implementation commitment.
11
+
12
+ Automation dispositions are:
13
+
14
+ - `implement`: deterministic, valuable, and expected to have a low false-positive rate under the
15
+ current contract.
16
+ - `research`: potentially valuable, but blocked on a reliable detection contract or measured
17
+ false-positive evidence. It remains off and is not on the implementation roadmap.
18
+ - `guidance`: valuable architectural advice that should not be a static diagnostic in its current
19
+ formulation. It remains here as a decision record.
20
+
21
+ Generic JavaScript, TypeScript, React, Vitest, accessibility, and import rules remain owned by Oxlint
22
+ and `@astrale-os/ox`; they are not duplicated here. Behavioral and live properties belong in tests or
23
+ release checks rather than this static-rule registry.
24
+
25
+ ## Executable status
26
+
27
+ | Rule | Owner | Status |
28
+ | --- | --- | --- |
29
+ | `astrale/thin-schema-composition-root` | `analyzer` | enforced |
30
+ | `astrale/one-remote-definition-per-file` | `analyzer` | enforced |
31
+ | `astrale/core-is-pure` | `analyzer` | enforced |
32
+ | `astrale/core-has-no-async` | `oxlint` | enforced |
33
+ | `astrale/no-nested-steps` | `oxlint` | enforced |
34
+ | `astrale/no-read-inside-mutate` | `oxlint` | enforced |
35
+
36
+ An `implement` disposition records that a policy passed the automation-admission judgment; it is not a
37
+ claim that its SDK or analyzer implementation exists.
38
+
39
+ ## Structure
40
+
41
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
42
+ | --- | --- | --- | --- | --- | --- | --- |
43
+ | `thin-schema-composition-root` | `analyzer` | `project` | `implement` | error | none | `schema/index.ts` may import, re-export, assemble `defineSchema`, and compile `D`; schema member constructors such as `nodeClass`, `edgeClass`, `nodeInterface`, `edgeInterface`, `fn`, and `func` belong in owned modules. |
44
+ | `thin-domain-composition-root` | `analyzer` | `project` | `research` | off | none | The domain root may assemble schema, methods, functions, deps, views, manifest, and install metadata but must not implement their behavior. |
45
+ | `bounded-context-layer-alignment` | `analyzer` | `project` | `guidance` | off | none | Semantic contexts discovered under schema have their owned runtime/core artifacts under the same context name; composition files are excluded. |
46
+ | `one-remote-definition-per-file` | `analyzer` | `file` | `implement` | error | none | A source file may contain at most one Astrale callable definition through `remoteMethod` or `defineRemoteFunction`; ordinary functions, callbacks, validators, and local helpers do not count. |
47
+ | `explicit-callable-filename` | `analyzer` | `file` | `research` | off | none | A callable file uses a verb-object name such as `assign-issue.ts`, not a bare verb such as `assign.ts`; detection compares registered vocabulary with the filename. |
48
+ | `no-technical-context-bucket` | `analyzer` | `project` | `guidance` | off | none | Domain layers should avoid dumping-ground paths such as `schema/functions`, `edges.ts`, `contracts.ts`, `runtime/handlers.ts`, or generic `helpers`/`utils` containers. |
49
+ | `context-owned-errors` | `analyzer` | `project` | `guidance` | off | none | Context-specific errors should live in `core/<context>/errors.ts`; only cross-context errors should live in `core/errors.ts`. Runtime may throw but should not define them. |
50
+ | `context-owned-paths` | `analyzer` | `project` | `guidance` | off | none | Exported graph path constants and constructors should live in `core/<context>/paths.ts` under the bounded context that owns the resource, not in runtime handlers. |
51
+ | `simulation-data-isolated` | `analyzer` | `project` | `research` | off | none | Fake data, samples, capture kernels, and demo scenarios live under top-level `simulation/`, and production layers must not import them. |
52
+ | `tests-follow-layer-ownership` | `analyzer` | `project` | `guidance` | off | none | Focused tests should live in the owning layer's `__tests__`; cross-context business workflows should live under `simulation/`. |
53
+ | `explicit-default-props-name` | `analyzer` | `file` | `guidance` | off | none | Default-property builders should live in `properties.defaults.ts` and use an explicit `default<Entity>Props` name such as `defaultCommentProps`. |
54
+ | `semantic-constant-filename` | `analyzer` | `file` | `guidance` | off | none | Exported vocabulary constants should live in a semantic file such as `tags/colors.constants.ts`, not a context-wide `constants.ts` dumping ground. |
55
+
56
+ ## Purity
57
+
58
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
59
+ | --- | --- | --- | --- | --- | --- | --- |
60
+ | `core-is-pure` | `analyzer` | `project` | `implement` | error | none | Production files under `core/` must not reach known effectful Astrale/Node packages or the project's `runtime`, `functions`, `integrations`, `client`, or `simulation` layers; detection follows resolved relative imports and excludes tests. Unknown third-party packages are not guessed to be effectful. |
61
+ | `core-has-no-async` | `oxlint` | `file` | `implement` | error | none | Production files under `core/` must not declare async functions or async generators, or use top-level `await`/`for await`; tests are excluded. |
62
+ | `integrations-enter-through-deps` | `analyzer` | `project` | `research` | off | none | Runtime handlers receive external clients through the domain dependency container and must not construct provider clients or import configured singletons directly. |
63
+ | `deps-construction-has-no-effects` | `oxlint` | `file` | `research` | off | none | The dependency factory may construct clients from configuration but must not await, fetch, call the kernel, read time/randomness, or perform request-specific I/O. |
64
+
65
+ ## Schema
66
+
67
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
68
+ | --- | --- | --- | --- | --- | --- | --- |
69
+ | `path-schema-for-addresses` | `analyzer` | `node` | `research` | off | none | Schema parameters representing graph addresses use `pathSchema()` or `absolutePathSchema()`, not `z.string()`; name-only guesses cannot emit this diagnostic. |
70
+ | `pathlike-at-input-boundaries` | `sdk` | `node` | `implement` | error | none | Public helpers that accept graph addresses use `Path` or `PathLike`; SDK-owned graph APIs must not publish `ref: string`-style contracts. |
71
+ | `schema-derived-handler-types` | `oxlint` | `file` | `research` | off | none | Callable implementations use `remoteMethod`/`defineRemoteFunction` schema-derived types and must not redeclare parallel params, result, self, or context interfaces. |
72
+ | `exact-runtime-callable-map` | `sdk` | `project` | `implement` | error | none | Methods and standalone-function maps contain every and only the callables declared by the compiled schema. |
73
+ | `every-member-wired` | `sdk` | `project` | `implement` | error | none | Every declared method, standalone function, view, post-install function, and referenced dependency is present in the compiled install/runtime definition. |
74
+ | `client-imports-schema-package` | `analyzer` | `project` | `research` | off | none | A separate frontend imports the published schema package and client surfaces; it must not reach through worker source for schema, core, runtime, deps, functions, or integrations. |
75
+
76
+ ## Authorization
77
+
78
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
79
+ | --- | --- | --- | --- | --- | --- | --- |
80
+ | `authorize-is-explicit` | `sdk` | `node` | `implement` | error | suggestion | Every method and standalone function declares `authorize`; an intentionally open callable uses an explicit empty hook. |
81
+ | `caller-gate-names-principal` | `analyzer` | `file` | `research` | off | none | A caller-sensitive authorization check explicitly passes `auth.principal` as `who`; success through the composed handler kernel is not caller proof. |
82
+
83
+ ## Durability
84
+
85
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
86
+ | --- | --- | --- | --- | --- | --- | --- |
87
+ | `effects-run-in-step` | `analyzer` | `project` | `research` | off | none | Every call path from a callable to a known kernel operation, dependency I/O, clock read, randomness source, or registered effect crosses exactly one `step.run` boundary. |
88
+ | `stable-step-identifier` | `analyzer` | `project` | `research` | off | none | Every executed `step.run` identifier resolves to stable kebab-case vocabulary; helper-owned steps may receive an identifier from callers only when every reachable call site supplies stable evidence. |
89
+ | `no-nested-steps` | `oxlint` | `node` | `implement` | error | none | A `step.run` callback must not directly invoke another `step.run`; interprocedural double wrapping is covered by `astrale/effects-run-in-step`. |
90
+ | `serializable-step-result` | `sdk` | `node` | `implement` | error | none | Values crossing a step boundary conform to the SDK's durable JSON-value contract; clients, functions, symbols, bigint, paths, bound nodes, and cyclic instances are rejected. |
91
+
92
+ ## Graph
93
+
94
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
95
+ | --- | --- | --- | --- | --- | --- | --- |
96
+ | `prefer-schema-bound-client` | `oxlint` | `file` | `research` | off | none | When a handler kernel is schema-bound, ordinary class reads, methods, links, and properties use the typed surface; raw calls require an unsupported or dynamic capability. |
97
+ | `raw-path-only-at-boundary` | `analyzer` | `node` | `research` | off | none | `.raw` is used only for serialization, wire protocols, persistence keys, or APIs requiring strings, not for ordinary equality or typed graph calls. |
98
+ | `no-redundant-raw-of` | `analyzer` | `node` | `implement` | error | suggestion | `rawOf()` receives a genuine `PathLike` boundary; values already typed as `Path` use `.raw`, and equality uses `.equals()`. |
99
+ | `no-read-inside-mutate` | `oxlint` | `node` | `implement` | error | none | A `kernel.mutate` builder callback must not be async or directly invoke a known graph-read method on the same kernel receiver; only patch construction belongs inside it. Arbitrary external I/O is not guessed by this file-local rule. |
100
+ | `do-not-reread-hydrated-node` | `analyzer` | `file` | `research` | off | none | Nodes returned by `children`, `neighbors`, `query`, or a bound read must not be immediately fetched again without an explicit fresh-snapshot reason. |
101
+ | `pagination-intent-is-explicit` | `analyzer` | `file` | `research` | off | none | Code consuming a paged result explicitly chooses first-page access, draining, continuation, cursor persistence, or async iteration. |
102
+ | `no-raw-database-query` | `oxlint` | `file` | `implement` | error | none | Domain code must not issue Cypher or adapter-specific database queries; it uses the typed graph API or supported query AST. |
103
+
104
+ ## Errors
105
+
106
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
107
+ | --- | --- | --- | --- | --- | --- | --- |
108
+ | `known-failures-use-domain-errors` | `analyzer` | `file` | `guidance` | off | none | Known not-found, invalid-transition, conflict, occupied-path, and relation failures should throw context-owned domain errors rather than generic `Error`. |
109
+ | `domain-error-has-stable-code` | `sdk` | `node` | `implement` | error | none | Every exported domain error exposes a non-empty stable machine-readable code through the SDK error contract; the linter does not infer which context fields the business requires. |
110
+ | `unknown-errors-propagate` | `analyzer` | `node` | `research` | off | none | A catch block must not translate every unknown kernel/provider failure into one generic domain error; only recognized failures may be mapped. |
111
+
112
+ ## Client
113
+
114
+ | Slug | Owner | Scope | Automation | Default | Fix | Invariant and detection contract |
115
+ | --- | --- | --- | --- | --- | --- | --- |
116
+ | `use-canonical-shell-bridge` | `analyzer` | `project` | `research` | off | none | A domain client uses `@astrale-os/shell-react` and must not contain a local copy of session, intent-router, graph-memory, or Shell handshake infrastructure. |
117
+
118
+ ## Automation rationale
119
+
120
+ ### Research before implementation
121
+
122
+ | Slug | What must be proven first |
123
+ | --- | --- |
124
+ | `thin-domain-composition-root` | Syntax alone does not reliably distinguish composition from small domain behavior; the accepted contract needs explicit allowed and forbidden declarations. |
125
+ | `explicit-callable-filename` | Callable identity must come from a resolved registration symbol rather than filename or verb guessing. |
126
+ | `simulation-data-isolated` | A narrow production-imports-simulation rule is detectable, but identifying fake data by names or contents is not; the policy should be split before acceptance. |
127
+ | `integrations-enter-through-deps` | Detection needs SDK-owned dependency and integration identities; arbitrary client imports and factories have legitimate uses. |
128
+ | `deps-construction-has-no-effects` | The linter must identify the dependency factory and known effect calls precisely instead of banning common syntax in similarly named files. |
129
+ | `path-schema-for-addresses` | Graph-address intent must be visible in compiled schema metadata; parameter names such as `ref` or `path` are insufficient evidence. |
130
+ | `schema-derived-handler-types` | The analyzer must distinguish duplicate callable contracts from legitimate local helper types. |
131
+ | `client-imports-schema-package` | Package and deployment boundaries must be discovered reliably before source imports can be judged as worker reach-through; relative imports from a client into backend `core/` are part of the forbidden boundary. |
132
+ | `caller-gate-names-principal` | The rule needs exact authorization-call identity and proof that the operation is caller-sensitive. |
133
+ | `effects-run-in-step` | Reliable enforcement requires interprocedural effect summaries, known effect identities, and explicit step ownership across helpers. |
134
+ | `stable-step-identifier` | File-local literal enforcement rejects valid helper-owned steps such as `readIssue(..., 'read-created-issue')`; project analysis or an SDK-branded identifier is required to prove every call site. |
135
+ | `prefer-schema-bound-client` | A warning is valid only when the analyzer can prove the typed client offers the equivalent capability. |
136
+ | `raw-path-only-at-boundary` | Type and callee-signature evidence must distinguish serialization boundaries from ordinary typed graph work. |
137
+ | `do-not-reread-hydrated-node` | Dataflow must prove that the same hydrated value is reread and provide a machine-visible way to request a fresh snapshot. |
138
+ | `pagination-intent-is-explicit` | The SDK must expose paged-result intent in types or methods that the analyzer can follow without guessing control flow. |
139
+ | `unknown-errors-propagate` | The analyzer must recognize typed error guards and mappings before it can distinguish selective translation from catch-all masking. |
140
+ | `use-canonical-shell-bridge` | Import requirements are detectable, but semantic copies of Shell infrastructure are not; the rule needs a narrower import-boundary contract. |
141
+
142
+ ### Keep as guidance in the current formulation
143
+
144
+ | Slug | Why static enforcement would be misleading |
145
+ | --- | --- |
146
+ | `bounded-context-layer-alignment` | Context ownership is business semantics, and valid layers may intentionally use different projections or context boundaries. |
147
+ | `no-technical-context-bucket` | Names such as `helpers`, `contracts`, or `edges` can indicate poor design but are not violations without understanding their contents and consumers. |
148
+ | `context-owned-errors` | A machine cannot reliably decide whether an error is context-specific or genuinely shared. |
149
+ | `context-owned-paths` | Resource ownership and the correct bounded context are architectural decisions; filenames cannot prove them. A narrower ban on runtime-local path declarations may be proposed separately. |
150
+ | `tests-follow-layer-ownership` | The distinction between a focused layer test and a business scenario depends on test intent, not directory names alone. |
151
+ | `explicit-default-props-name` | The exact builder name and filename are conventions whose value rarely justifies false positives across valid construction patterns. |
152
+ | `semantic-constant-filename` | Whether a constants file is a dumping ground depends on cohesion and consumers, not the word `constants`. |
153
+ | `known-failures-use-domain-errors` | Identifying a known business failure requires domain semantics; a generic `Error` ban would also reject legitimate infrastructure and assertion failures. |
@@ -0,0 +1,14 @@
1
+ export { ruleCatalog, ruleId, ruleInfo } from './rules/catalog.js'
2
+ export type { ImplementedRuleSlug, RuleInfo, RuleOwner } from './rules/catalog.js'
3
+ export type {
4
+ DiagnosticLocation,
5
+ DiagnosticSeverity,
6
+ LintDiagnostic,
7
+ LintResult,
8
+ } from './diagnostic.js'
9
+ export { LinterToolError } from './diagnostic.js'
10
+ export { lintDomain } from './lint.js'
11
+ export type { LintDomainOptions } from './lint.js'
12
+ export { domainOxlintConfig } from './oxlint/config.js'
13
+ export { formatLintResult } from './report.js'
14
+ export type { LintReportFormat } from './report.js'
@@ -0,0 +1,32 @@
1
+ import { resolve } from 'node:path'
2
+ import { performance } from 'node:perf_hooks'
3
+
4
+ import { analyzeProject } from './analyze.js'
5
+ import { dedupeDiagnostics, type LintResult } from './diagnostic.js'
6
+ import { runOxlint } from './oxlint/run.js'
7
+ import { assertSingleTypeUniverse } from './preflight.js'
8
+ import { discoverProject } from './project.js'
9
+
10
+ export type LintDomainOptions = {
11
+ root?: string
12
+ fix?: boolean
13
+ preflight?: boolean
14
+ }
15
+
16
+ export async function lintDomain(options: LintDomainOptions = {}): Promise<LintResult> {
17
+ const started = performance.now()
18
+ const root = resolve(options.root ?? process.cwd())
19
+ if (options.preflight !== false) await assertSingleTypeUniverse(root)
20
+ const [project, oxlint] = await Promise.all([
21
+ discoverProject(root),
22
+ runOxlint(root, options.fix ?? false),
23
+ ])
24
+ const diagnostics = dedupeDiagnostics([...oxlint.diagnostics, ...analyzeProject(project)])
25
+ return {
26
+ root: project.root,
27
+ diagnostics,
28
+ files: Math.max(project.files.length, oxlint.files),
29
+ durationMs: Math.max(0, Math.round(performance.now() - started)),
30
+ exitCode: diagnostics.some((diagnostic) => diagnostic.severity === 'error') ? 1 : 0,
31
+ }
32
+ }
@@ -0,0 +1,5 @@
1
+ declare module '@astrale-os/ox/lint' {
2
+ // oxlint-disable-next-line consistent-type-imports -- ambient modules cannot import this type
3
+ const config: import('oxlint').OxlintConfig
4
+ export default config
5
+ }
@@ -0,0 +1,19 @@
1
+ import base from '@astrale-os/ox/lint'
2
+ import { defineConfig } from 'oxlint'
3
+
4
+ export const domainOxlintConfig = defineConfig({
5
+ extends: [base],
6
+ jsPlugins: [
7
+ {
8
+ name: 'astrale',
9
+ specifier: '@astrale-os/sdk/linter/oxlint-plugin',
10
+ },
11
+ ],
12
+ rules: {
13
+ 'astrale/core-has-no-async': 'error',
14
+ 'astrale/no-nested-steps': 'error',
15
+ 'astrale/no-read-inside-mutate': 'error',
16
+ },
17
+ })
18
+
19
+ export default domainOxlintConfig
@@ -0,0 +1,16 @@
1
+ // @ts-check
2
+
3
+ import { coreHasNoAsync } from '../rules/core-has-no-async.js'
4
+ import { noNestedSteps } from '../rules/no-nested-steps.js'
5
+ import { noReadInsideMutate } from '../rules/no-read-inside-mutate.js'
6
+
7
+ const astraleOxlintPlugin = {
8
+ meta: { name: 'astrale', version: '0.1.0' },
9
+ rules: {
10
+ 'core-has-no-async': coreHasNoAsync,
11
+ 'no-nested-steps': noNestedSteps,
12
+ 'no-read-inside-mutate': noReadInsideMutate,
13
+ },
14
+ }
15
+
16
+ export default astraleOxlintPlugin
@@ -0,0 +1,162 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { createRequire } from 'node:module'
4
+ import { dirname, join, relative, resolve, sep } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import { LinterToolError, type DiagnosticSeverity, type LintDiagnostic } from '../diagnostic.js'
8
+ import { ruleInfo } from '../rules/catalog.js'
9
+
10
+ type OxlintOutput = {
11
+ diagnostics?: OxlintDiagnostic[]
12
+ number_of_files?: number
13
+ }
14
+
15
+ type OxlintDiagnostic = {
16
+ message?: string
17
+ code?: string
18
+ severity?: string
19
+ url?: string
20
+ help?: string
21
+ filename?: string
22
+ labels?: Array<{
23
+ span?: { offset?: number; length?: number; line?: number; column?: number }
24
+ }>
25
+ }
26
+
27
+ export type OxlintResult = {
28
+ diagnostics: LintDiagnostic[]
29
+ files: number
30
+ }
31
+
32
+ export async function runOxlint(root: string, fix: boolean): Promise<OxlintResult> {
33
+ const packageJson = join(root, 'package.json')
34
+ const projectRequire = createRequire(packageJson)
35
+ const sdkRequire = createRequire(import.meta.url)
36
+ const oxlintPackage = resolvePackage('oxlint/package.json', projectRequire, sdkRequire)
37
+ const bin = join(dirname(oxlintPackage), 'bin', 'oxlint')
38
+ const config = projectConfig(root) ?? internalConfigPath()
39
+ const args = [bin, '.', '--format=json', '--no-error-on-unmatched-pattern', '--config', config]
40
+ if (fix) args.push('--fix')
41
+
42
+ const child = spawn(process.execPath, args, {
43
+ cwd: root,
44
+ env: process.env,
45
+ stdio: ['ignore', 'pipe', 'pipe'],
46
+ })
47
+ let stdout = ''
48
+ let stderr = ''
49
+ child.stdout.setEncoding('utf8')
50
+ child.stderr.setEncoding('utf8')
51
+ child.stdout.on('data', (chunk: string) => (stdout += chunk))
52
+ child.stderr.on('data', (chunk: string) => (stderr += chunk))
53
+ const status = await new Promise<number | null>((resolveStatus, reject) => {
54
+ child.once('error', reject)
55
+ child.once('close', resolveStatus)
56
+ }).catch((error: unknown) => {
57
+ throw new LinterToolError(`Could not start Oxlint at ${bin}.`, { cause: error })
58
+ })
59
+
60
+ let output: OxlintOutput
61
+ try {
62
+ output = JSON.parse(stdout) as OxlintOutput
63
+ } catch (error) {
64
+ const detail =
65
+ stderr.trim() || stdout.trim() || `Oxlint exited with status ${status ?? 'unknown'}`
66
+ throw new LinterToolError(`Oxlint did not produce a diagnostic report.\n${detail}`, {
67
+ cause: error,
68
+ })
69
+ }
70
+ if (status !== 0 && status !== 1) {
71
+ throw new LinterToolError(
72
+ `Oxlint failed with status ${status ?? 'unknown'}.${stderr.trim() ? `\n${stderr.trim()}` : ''}`,
73
+ )
74
+ }
75
+
76
+ return {
77
+ diagnostics: (output.diagnostics ?? []).map((diagnostic) =>
78
+ normalizeDiagnostic(root, diagnostic),
79
+ ),
80
+ files: output.number_of_files ?? 0,
81
+ }
82
+ }
83
+
84
+ function resolvePackage(
85
+ id: string,
86
+ projectRequire: NodeJS.Require,
87
+ sdkRequire: NodeJS.Require,
88
+ ): string {
89
+ try {
90
+ return projectRequire.resolve(id)
91
+ } catch (projectError) {
92
+ try {
93
+ return sdkRequire.resolve(id)
94
+ } catch {
95
+ throw new LinterToolError(
96
+ 'Oxlint is not installed. Add the scaffolded Oxlint dev dependency and run your package manager install.',
97
+ { cause: projectError },
98
+ )
99
+ }
100
+ }
101
+ }
102
+
103
+ function projectConfig(root: string): string | undefined {
104
+ for (const name of [
105
+ 'oxlint.config.ts',
106
+ 'oxlint.config.js',
107
+ 'oxlint.config.mjs',
108
+ 'oxlint.config.cjs',
109
+ '.oxlintrc.json',
110
+ '.oxlintrc.jsonc',
111
+ ]) {
112
+ const path = join(root, name)
113
+ if (existsSync(path)) return path
114
+ }
115
+ return undefined
116
+ }
117
+
118
+ function internalConfigPath(): string {
119
+ const current = fileURLToPath(import.meta.url)
120
+ const extension = current.endsWith('.ts') ? '.ts' : '.js'
121
+ return join(dirname(current), `config${extension}`)
122
+ }
123
+
124
+ function normalizeDiagnostic(root: string, diagnostic: OxlintDiagnostic): LintDiagnostic {
125
+ const id = normalizeCode(diagnostic.code ?? 'oxlint/unknown')
126
+ const info = ruleInfo(id)
127
+ const span = diagnostic.labels?.[0]?.span
128
+ return {
129
+ id,
130
+ severity: normalizeSeverity(diagnostic.severity),
131
+ message: diagnostic.message ?? info?.message ?? 'Oxlint reported a violation.',
132
+ ...(diagnostic.help || info?.help ? { help: diagnostic.help ?? info?.help } : {}),
133
+ ...(diagnostic.url || info?.url ? { url: diagnostic.url ?? info?.url } : {}),
134
+ owner: info?.owner ?? 'generic',
135
+ ...(diagnostic.filename
136
+ ? {
137
+ location: {
138
+ path: relativePath(root, diagnostic.filename),
139
+ line: span?.line ?? 1,
140
+ column: span?.column ?? 1,
141
+ ...(span?.offset !== undefined ? { offset: span.offset } : {}),
142
+ ...(span?.length !== undefined ? { length: span.length } : {}),
143
+ },
144
+ }
145
+ : {}),
146
+ }
147
+ }
148
+
149
+ function normalizeCode(code: string): string {
150
+ const match = /^([^()]+)\(([^()]+)\)$/.exec(code)
151
+ return match ? `${match[1]}/${match[2]}` : code
152
+ }
153
+
154
+ function normalizeSeverity(severity: string | undefined): DiagnosticSeverity {
155
+ return severity === 'error' ? 'error' : 'warning'
156
+ }
157
+
158
+ function relativePath(root: string, filename: string): string {
159
+ const absolute = resolve(root, filename)
160
+ const path = relative(root, absolute)
161
+ return sep === '/' ? path : path.split(sep).join('/')
162
+ }