@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.
- package/dist/auth/authenticate.d.ts +1 -1
- package/dist/auth/authenticate.d.ts.map +1 -1
- package/dist/auth/authenticate.js +2 -2
- package/dist/auth/authenticate.js.map +1 -1
- package/dist/auth/check.js +1 -1
- package/dist/auth/check.js.map +1 -1
- package/dist/auth/function-context.d.ts +1 -1
- package/dist/auth/function-context.d.ts.map +1 -1
- package/dist/auth/function-context.js +3 -3
- package/dist/auth/function-context.js.map +1 -1
- package/dist/auth/index.d.ts +15 -15
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +10 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/issuer-mint.d.ts +1 -1
- package/dist/auth/issuer-mint.d.ts.map +1 -1
- package/dist/auth/issuer-mint.js +2 -2
- package/dist/auth/issuer-mint.js.map +1 -1
- package/dist/auth/kernel-client.d.ts +1 -1
- package/dist/auth/kernel-client.d.ts.map +1 -1
- package/dist/auth/kernel-client.js +2 -2
- package/dist/auth/kernel-client.js.map +1 -1
- package/dist/auth/resolve.d.ts +2 -2
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +2 -2
- package/dist/auth/resolve.js.map +1 -1
- package/dist/auth/verify.d.ts +1 -1
- package/dist/auth/verify.d.ts.map +1 -1
- package/dist/auth/verify.js +2 -2
- package/dist/auth/verify.js.map +1 -1
- package/dist/cli/bin.js +1 -1
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/publish.js +1 -1
- package/dist/cli/publish.js.map +1 -1
- package/dist/cli/run.d.ts +9 -3
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +55 -5
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/spec.d.ts +1 -1
- package/dist/cli/spec.d.ts.map +1 -1
- package/dist/cli/spec.js +1 -1
- package/dist/cli/spec.js.map +1 -1
- package/dist/config/define-domain.d.ts +2 -2
- package/dist/config/define-domain.d.ts.map +1 -1
- package/dist/config/deploy.d.ts +2 -2
- package/dist/config/deploy.d.ts.map +1 -1
- package/dist/config/index.d.ts +6 -6
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/index.js.map +1 -1
- package/dist/define/index.d.ts +4 -4
- package/dist/define/index.d.ts.map +1 -1
- package/dist/define/index.js +2 -2
- package/dist/define/index.js.map +1 -1
- package/dist/define/remote-function.d.ts +7 -5
- package/dist/define/remote-function.d.ts.map +1 -1
- package/dist/define/remote-function.js.map +1 -1
- package/dist/define/view.d.ts +3 -3
- package/dist/define/view.d.ts.map +1 -1
- package/dist/deploy/check.d.ts +1 -1
- package/dist/deploy/check.d.ts.map +1 -1
- package/dist/deploy/check.js +1 -1
- package/dist/deploy/check.js.map +1 -1
- package/dist/deploy/index.d.ts +2 -2
- package/dist/deploy/index.d.ts.map +1 -1
- package/dist/deploy/index.js +2 -2
- package/dist/deploy/index.js.map +1 -1
- package/dist/dispatch/authorize.js +1 -1
- package/dist/dispatch/authorize.js.map +1 -1
- package/dist/dispatch/dispatcher.d.ts +11 -21
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +66 -65
- package/dist/dispatch/dispatcher.js.map +1 -1
- package/dist/dispatch/execute.d.ts +7 -6
- package/dist/dispatch/execute.d.ts.map +1 -1
- package/dist/dispatch/execute.js +1 -0
- package/dist/dispatch/execute.js.map +1 -1
- package/dist/dispatch/identity.d.ts +2 -2
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/index.d.ts +6 -6
- package/dist/dispatch/index.d.ts.map +1 -1
- package/dist/dispatch/index.js +6 -6
- package/dist/dispatch/index.js.map +1 -1
- package/dist/dispatch/resolve.d.ts +1 -1
- package/dist/dispatch/resolve.d.ts.map +1 -1
- package/dist/dispatch/self.d.ts +1 -1
- package/dist/dispatch/self.d.ts.map +1 -1
- package/dist/domain/build-spec.d.ts +1 -1
- package/dist/domain/build-spec.d.ts.map +1 -1
- package/dist/domain/build-spec.js +2 -2
- package/dist/domain/build-spec.js.map +1 -1
- package/dist/domain/contract.d.ts +1 -1
- package/dist/domain/contract.d.ts.map +1 -1
- package/dist/domain/define.d.ts +4 -4
- package/dist/domain/define.d.ts.map +1 -1
- package/dist/domain/define.js +2 -2
- package/dist/domain/define.js.map +1 -1
- package/dist/domain/extend-functions.d.ts +1 -1
- package/dist/domain/extend-functions.d.ts.map +1 -1
- package/dist/domain/extend-functions.js +1 -1
- package/dist/domain/extend-functions.js.map +1 -1
- package/dist/domain/extend-views.d.ts +1 -1
- package/dist/domain/extend-views.d.ts.map +1 -1
- package/dist/domain/extend-views.js +1 -1
- package/dist/domain/extend-views.js.map +1 -1
- package/dist/domain/index.d.ts +4 -4
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +3 -3
- package/dist/domain/index.js.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/linter/analyze.d.ts +6 -0
- package/dist/linter/analyze.d.ts.map +1 -0
- package/dist/linter/analyze.js +12 -0
- package/dist/linter/analyze.js.map +1 -0
- package/dist/linter/diagnostic.d.ts +32 -0
- package/dist/linter/diagnostic.d.ts.map +1 -0
- package/dist/linter/diagnostic.js +35 -0
- package/dist/linter/diagnostic.js.map +1 -0
- package/dist/linter/index.d.ts +10 -0
- package/dist/linter/index.d.ts.map +1 -0
- package/dist/linter/index.js +6 -0
- package/dist/linter/index.js.map +1 -0
- package/dist/linter/lint.d.ts +8 -0
- package/dist/linter/lint.d.ts.map +1 -0
- package/dist/linter/lint.js +26 -0
- package/dist/linter/lint.js.map +1 -0
- package/dist/linter/oxlint/config.d.ts +14 -0
- package/dist/linter/oxlint/config.d.ts.map +1 -0
- package/dist/linter/oxlint/config.js +18 -0
- package/dist/linter/oxlint/config.js.map +1 -0
- package/dist/linter/oxlint/plugin.d.ts +13 -0
- package/dist/linter/oxlint/plugin.d.ts.map +1 -0
- package/dist/linter/oxlint/plugin.js +14 -0
- package/dist/linter/oxlint/plugin.js.map +1 -0
- package/dist/linter/oxlint/run.d.ts +7 -0
- package/dist/linter/oxlint/run.d.ts.map +1 -0
- package/dist/linter/oxlint/run.js +122 -0
- package/dist/linter/oxlint/run.js.map +1 -0
- package/dist/linter/preflight.d.ts +2 -0
- package/dist/linter/preflight.d.ts.map +1 -0
- package/dist/linter/preflight.js +162 -0
- package/dist/linter/preflight.js.map +1 -0
- package/dist/linter/project.d.ts +12 -0
- package/dist/linter/project.d.ts.map +1 -0
- package/dist/linter/project.js +88 -0
- package/dist/linter/project.js.map +1 -0
- package/dist/linter/report.d.ts +4 -0
- package/dist/linter/report.d.ts.map +1 -0
- package/dist/linter/report.js +22 -0
- package/dist/linter/report.js.map +1 -0
- package/dist/linter/rules/catalog.d.ts +56 -0
- package/dist/linter/rules/catalog.d.ts.map +1 -0
- package/dist/linter/rules/catalog.js +53 -0
- package/dist/linter/rules/catalog.js.map +1 -0
- package/dist/linter/rules/core-has-no-async.d.ts +3 -0
- package/dist/linter/rules/core-has-no-async.d.ts.map +1 -0
- package/dist/linter/rules/core-has-no-async.js +43 -0
- package/dist/linter/rules/core-has-no-async.js.map +1 -0
- package/dist/linter/rules/core-is-pure.d.ts +3 -0
- package/dist/linter/rules/core-is-pure.d.ts.map +1 -0
- package/dist/linter/rules/core-is-pure.js +74 -0
- package/dist/linter/rules/core-is-pure.js.map +1 -0
- package/dist/linter/rules/no-nested-steps.d.ts +3 -0
- package/dist/linter/rules/no-nested-steps.d.ts.map +1 -0
- package/dist/linter/rules/no-nested-steps.js +32 -0
- package/dist/linter/rules/no-nested-steps.js.map +1 -0
- package/dist/linter/rules/no-read-inside-mutate.d.ts +3 -0
- package/dist/linter/rules/no-read-inside-mutate.d.ts.map +1 -0
- package/dist/linter/rules/no-read-inside-mutate.js +64 -0
- package/dist/linter/rules/no-read-inside-mutate.js.map +1 -0
- package/dist/linter/rules/one-remote-definition-per-file.d.ts +3 -0
- package/dist/linter/rules/one-remote-definition-per-file.d.ts.map +1 -0
- package/dist/linter/rules/one-remote-definition-per-file.js +23 -0
- package/dist/linter/rules/one-remote-definition-per-file.js.map +1 -0
- package/dist/linter/rules/oxlint-ast.d.ts +30 -0
- package/dist/linter/rules/oxlint-ast.d.ts.map +1 -0
- package/dist/linter/rules/oxlint-ast.js +104 -0
- package/dist/linter/rules/oxlint-ast.js.map +1 -0
- package/dist/linter/rules/oxlint-rule.d.ts +45 -0
- package/dist/linter/rules/oxlint-rule.d.ts.map +1 -0
- package/dist/linter/rules/oxlint-rule.js +2 -0
- package/dist/linter/rules/oxlint-rule.js.map +1 -0
- package/dist/linter/rules/project-rule.d.ts +10 -0
- package/dist/linter/rules/project-rule.d.ts.map +1 -0
- package/dist/linter/rules/project-rule.js +24 -0
- package/dist/linter/rules/project-rule.js.map +1 -0
- package/dist/linter/rules/thin-schema-composition-root.d.ts +3 -0
- package/dist/linter/rules/thin-schema-composition-root.d.ts.map +1 -0
- package/dist/linter/rules/thin-schema-composition-root.js +23 -0
- package/dist/linter/rules/thin-schema-composition-root.js.map +1 -0
- package/dist/linter/source.d.ts +24 -0
- package/dist/linter/source.d.ts.map +1 -0
- package/dist/linter/source.js +279 -0
- package/dist/linter/source.js.map +1 -0
- package/dist/linter/suppression.d.ts +3 -0
- package/dist/linter/suppression.d.ts.map +1 -0
- package/dist/linter/suppression.js +11 -0
- package/dist/linter/suppression.js.map +1 -0
- package/dist/method/class.d.ts +3 -3
- package/dist/method/class.d.ts.map +1 -1
- package/dist/method/context.d.ts +6 -4
- package/dist/method/context.d.ts.map +1 -1
- package/dist/method/index.d.ts +5 -5
- package/dist/method/index.d.ts.map +1 -1
- package/dist/method/index.js +2 -2
- package/dist/method/index.js.map +1 -1
- package/dist/method/single.d.ts +2 -2
- package/dist/method/single.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.d.ts +3 -3
- package/dist/server/auxiliary-routes.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.js +11 -11
- package/dist/server/auxiliary-routes.js.map +1 -1
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/create.d.ts +2 -2
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/create.js +11 -11
- package/dist/server/create.js.map +1 -1
- package/dist/server/domain-entry.d.ts +5 -5
- package/dist/server/domain-entry.d.ts.map +1 -1
- package/dist/server/domain-entry.js +2 -2
- package/dist/server/domain-entry.js.map +1 -1
- package/dist/server/index.d.ts +11 -11
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +7 -7
- package/dist/server/index.js.map +1 -1
- package/dist/server/start.d.ts +1 -1
- package/dist/server/start.d.ts.map +1 -1
- package/dist/server/start.js.map +1 -1
- package/dist/server/worker-entry.d.ts +1 -1
- package/dist/server/worker-entry.d.ts.map +1 -1
- package/dist/server/worker-entry.js +3 -3
- package/dist/server/worker-entry.js.map +1 -1
- package/dist/step/index.d.ts +4 -4
- package/dist/step/index.d.ts.map +1 -1
- package/dist/step/index.js +2 -2
- package/dist/step/index.js.map +1 -1
- package/dist/step/inline.d.ts +1 -1
- package/dist/step/inline.d.ts.map +1 -1
- package/dist/step/inline.js +1 -1
- package/dist/step/inline.js.map +1 -1
- package/package.json +29 -8
- package/src/auth/authenticate.ts +3 -3
- package/src/auth/check.ts +1 -1
- package/src/auth/function-context.ts +4 -4
- package/src/auth/index.ts +15 -15
- package/src/auth/issuer-mint.ts +3 -3
- package/src/auth/kernel-client.ts +3 -3
- package/src/auth/resolve.ts +4 -4
- package/src/auth/verify.ts +3 -3
- package/src/cli/bin.ts +1 -1
- package/src/cli/index.ts +2 -2
- package/src/cli/publish.ts +1 -1
- package/src/cli/run.ts +59 -8
- package/src/cli/spec.ts +3 -3
- package/src/config/define-domain.ts +2 -2
- package/src/config/deploy.ts +2 -2
- package/src/config/index.ts +6 -6
- package/src/define/index.ts +4 -4
- package/src/define/remote-function.ts +7 -5
- package/src/define/view.ts +3 -3
- package/src/deploy/check.ts +2 -2
- package/src/deploy/index.ts +2 -2
- package/src/dispatch/authorize.ts +1 -1
- package/src/dispatch/dispatcher.ts +76 -83
- package/src/dispatch/execute.ts +8 -6
- package/src/dispatch/identity.ts +2 -2
- package/src/dispatch/index.ts +6 -6
- package/src/dispatch/resolve.ts +1 -1
- package/src/dispatch/self.ts +1 -1
- package/src/domain/build-spec.ts +4 -4
- package/src/domain/contract.ts +1 -1
- package/src/domain/define.ts +6 -6
- package/src/domain/extend-functions.ts +2 -2
- package/src/domain/extend-views.ts +2 -2
- package/src/domain/index.ts +4 -4
- package/src/index.ts +20 -20
- package/src/linter/analyze.ts +17 -0
- package/src/linter/diagnostic.ts +68 -0
- package/src/linter/docs/DIAGNOSTICS.md +47 -0
- package/src/linter/docs/JUDGMENT.md +81 -0
- package/src/linter/docs/PRINCIPLES.md +97 -0
- package/src/linter/docs/README.md +38 -0
- package/src/linter/docs/RULE-AUTHORING.md +144 -0
- package/src/linter/docs/RULES.md +153 -0
- package/src/linter/index.ts +14 -0
- package/src/linter/lint.ts +32 -0
- package/src/linter/oxlint/astrale-ox.d.ts +5 -0
- package/src/linter/oxlint/config.ts +19 -0
- package/src/linter/oxlint/plugin.js +16 -0
- package/src/linter/oxlint/run.ts +162 -0
- package/src/linter/preflight.ts +181 -0
- package/src/linter/project.ts +108 -0
- package/src/linter/report.ts +25 -0
- package/src/linter/rules/catalog.ts +67 -0
- package/src/linter/rules/core-has-no-async.js +43 -0
- package/src/linter/rules/core-is-pure.ts +103 -0
- package/src/linter/rules/no-nested-steps.js +37 -0
- package/src/linter/rules/no-read-inside-mutate.js +72 -0
- package/src/linter/rules/one-remote-definition-per-file.ts +29 -0
- package/src/linter/rules/oxlint-ast.js +110 -0
- package/src/linter/rules/oxlint-rule.ts +35 -0
- package/src/linter/rules/project-rule.ts +37 -0
- package/src/linter/rules/thin-schema-composition-root.ts +26 -0
- package/src/linter/source.ts +323 -0
- package/src/linter/suppression.ts +11 -0
- package/src/method/class.ts +3 -3
- package/src/method/context.ts +6 -4
- package/src/method/index.ts +5 -5
- package/src/method/single.ts +2 -2
- package/src/server/auxiliary-routes.ts +22 -17
- package/src/server/config.ts +1 -1
- package/src/server/create.ts +15 -15
- package/src/server/domain-entry.ts +7 -7
- package/src/server/index.ts +11 -11
- package/src/server/start.ts +3 -2
- package/src/server/worker-entry.ts +4 -4
- package/src/step/index.ts +4 -4
- package/src/step/inline.ts +2 -2
- package/dist/defer/index.d.ts +0 -24
- package/dist/defer/index.d.ts.map +0 -1
- package/dist/defer/index.js +0 -42
- package/dist/defer/index.js.map +0 -1
- 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,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
|
+
}
|