@ontrails/warden 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/CHANGELOG.md +1556 -0
  2. package/README.md +200 -0
  3. package/bin/warden.ts +51 -0
  4. package/package.json +51 -0
  5. package/src/adapter-check.ts +136 -0
  6. package/src/cli.ts +2159 -0
  7. package/src/command.ts +1167 -0
  8. package/src/config.ts +193 -0
  9. package/src/draft.ts +22 -0
  10. package/src/drift.ts +260 -0
  11. package/src/fix.ts +126 -0
  12. package/src/formatters.ts +178 -0
  13. package/src/guide.ts +245 -0
  14. package/src/index.ts +295 -0
  15. package/src/project-context.ts +446 -0
  16. package/src/project-rules.ts +290 -0
  17. package/src/regrade-history.ts +188 -0
  18. package/src/regrade-receipt.ts +484 -0
  19. package/src/resolve.ts +531 -0
  20. package/src/rules/activation-orphan.ts +97 -0
  21. package/src/rules/captured-kernel.ts +375 -0
  22. package/src/rules/circular-refs.ts +150 -0
  23. package/src/rules/cli-command-route-coherence.ts +177 -0
  24. package/src/rules/composes-declarations.ts +839 -0
  25. package/src/rules/context-no-surface-types.ts +214 -0
  26. package/src/rules/dead-internal-trail.ts +161 -0
  27. package/src/rules/dead-public-trail.ts +258 -0
  28. package/src/rules/draft-file-marking.ts +155 -0
  29. package/src/rules/draft-visible-debt.ts +83 -0
  30. package/src/rules/duplicate-exported-symbol.ts +172 -0
  31. package/src/rules/duplicate-public-contract.ts +137 -0
  32. package/src/rules/entity-exists.ts +254 -0
  33. package/src/rules/entity-ids.ts +15 -0
  34. package/src/rules/error-mapping-completeness.ts +290 -0
  35. package/src/rules/example-valid.ts +395 -0
  36. package/src/rules/fires-declarations.ts +740 -0
  37. package/src/rules/governed-symbol-residue.ts +535 -0
  38. package/src/rules/governed-vocabulary-permutation-watch.ts +77 -0
  39. package/src/rules/implementation-returns-result.ts +1733 -0
  40. package/src/rules/incomplete-accessor-for-standard-op.ts +272 -0
  41. package/src/rules/incomplete-crud.ts +583 -0
  42. package/src/rules/index.ts +328 -0
  43. package/src/rules/intent-propagation.ts +125 -0
  44. package/src/rules/layer-field-name-drift.ts +102 -0
  45. package/src/rules/library-render-coherence.ts +97 -0
  46. package/src/rules/metadata.ts +929 -0
  47. package/src/rules/missing-reconcile.ts +97 -0
  48. package/src/rules/missing-visibility.ts +111 -0
  49. package/src/rules/no-destructured-compose.ts +196 -0
  50. package/src/rules/no-dev-permit-in-source.ts +99 -0
  51. package/src/rules/no-direct-implementation-call.ts +52 -0
  52. package/src/rules/no-legacy-cli-alias-export.ts +247 -0
  53. package/src/rules/no-legacy-layer-imports.ts +211 -0
  54. package/src/rules/no-native-error-result.ts +118 -0
  55. package/src/rules/no-redundant-result-error-wrap.ts +382 -0
  56. package/src/rules/no-retired-cross-vocabulary.ts +203 -0
  57. package/src/rules/no-sync-result-assumption.ts +1199 -0
  58. package/src/rules/no-throw-in-detour-recover.ts +225 -0
  59. package/src/rules/no-throw-in-implementation.ts +48 -0
  60. package/src/rules/no-top-level-surface.ts +371 -0
  61. package/src/rules/on-references-exist.ts +194 -0
  62. package/src/rules/orphaned-signal.ts +149 -0
  63. package/src/rules/owner-render-parity.ts +143 -0
  64. package/src/rules/permit-governance.ts +25 -0
  65. package/src/rules/prefer-schema-inference.ts +141 -0
  66. package/src/rules/public-export-example-coverage.ts +561 -0
  67. package/src/rules/public-internal-deep-imports.ts +454 -0
  68. package/src/rules/public-output-schema.ts +29 -0
  69. package/src/rules/public-union-output-discriminants.ts +150 -0
  70. package/src/rules/read-intent-fires.ts +188 -0
  71. package/src/rules/reference-exists.ts +97 -0
  72. package/src/rules/registry-names.ts +171 -0
  73. package/src/rules/resolved-import-boundary.ts +146 -0
  74. package/src/rules/resource-declarations.ts +697 -0
  75. package/src/rules/resource-exists.ts +181 -0
  76. package/src/rules/resource-id-grammar.ts +65 -0
  77. package/src/rules/resource-mock-coverage.ts +115 -0
  78. package/src/rules/retired-vocabulary.ts +1407 -0
  79. package/src/rules/scan.ts +59 -0
  80. package/src/rules/scheduled-destroy-intent.ts +44 -0
  81. package/src/rules/signal-graph-coaching.ts +220 -0
  82. package/src/rules/source/composition.ts +165 -0
  83. package/src/rules/source/drafts.ts +164 -0
  84. package/src/rules/source/entities.ts +618 -0
  85. package/src/rules/source/pragmas.ts +45 -0
  86. package/src/rules/source/resources.ts +64 -0
  87. package/src/rules/source/signals.ts +397 -0
  88. package/src/rules/source/stores.ts +310 -0
  89. package/src/rules/specs.ts +388 -0
  90. package/src/rules/static-resource-accessor-preference.ts +654 -0
  91. package/src/rules/structure.ts +234 -0
  92. package/src/rules/surface-overlay-coherence.ts +262 -0
  93. package/src/rules/surface-trailhead-coherence.ts +366 -0
  94. package/src/rules/trail-fork-coaching.ts +625 -0
  95. package/src/rules/trail-versioning-source.ts +1076 -0
  96. package/src/rules/trail-versioning-topo.ts +172 -0
  97. package/src/rules/trailhead-override-divergence.ts +356 -0
  98. package/src/rules/types.ts +464 -0
  99. package/src/rules/unmaterialized-activation-source.ts +85 -0
  100. package/src/rules/unreachable-detour-shadowing.ts +343 -0
  101. package/src/rules/valid-describe-refs.ts +224 -0
  102. package/src/rules/valid-detour-contract.ts +78 -0
  103. package/src/rules/warden-export-symmetry.ts +540 -0
  104. package/src/rules/warden-rules-use-ast.ts +1109 -0
  105. package/src/rules/webhook-route-collision.ts +306 -0
  106. package/src/rules/workspace-lock-ownership.ts +34 -0
  107. package/src/trails/activation-orphan.trail.ts +84 -0
  108. package/src/trails/captured-kernel.trail.ts +108 -0
  109. package/src/trails/circular-refs.trail.ts +29 -0
  110. package/src/trails/cli-command-route-coherence.trail.ts +47 -0
  111. package/src/trails/composes-declarations.trail.ts +22 -0
  112. package/src/trails/context-no-surface-types.trail.ts +21 -0
  113. package/src/trails/dead-internal-trail.trail.ts +26 -0
  114. package/src/trails/dead-public-trail.trail.ts +31 -0
  115. package/src/trails/deprecation-without-guidance.trail.ts +21 -0
  116. package/src/trails/draft-file-marking.trail.ts +16 -0
  117. package/src/trails/draft-visible-debt.trail.ts +16 -0
  118. package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
  119. package/src/trails/duplicate-public-contract.trail.ts +47 -0
  120. package/src/trails/entity-exists.trail.ts +21 -0
  121. package/src/trails/error-mapping-completeness.trail.ts +30 -0
  122. package/src/trails/example-valid.trail.ts +25 -0
  123. package/src/trails/fires-declarations.trail.ts +23 -0
  124. package/src/trails/fork-without-preserved-implementation.trail.ts +31 -0
  125. package/src/trails/governed-symbol-residue.trail.ts +24 -0
  126. package/src/trails/governed-vocabulary-permutation-watch.trail.ts +16 -0
  127. package/src/trails/implementation-returns-result.trail.ts +20 -0
  128. package/src/trails/incomplete-accessor-for-standard-op.trail.ts +76 -0
  129. package/src/trails/incomplete-crud.trail.ts +39 -0
  130. package/src/trails/index.ts +91 -0
  131. package/src/trails/intent-propagation.trail.ts +30 -0
  132. package/src/trails/layer-field-name-drift.trail.ts +39 -0
  133. package/src/trails/library-render-coherence.trail.ts +43 -0
  134. package/src/trails/marker-schema-unsupported.trail.ts +23 -0
  135. package/src/trails/missing-reconcile.trail.ts +33 -0
  136. package/src/trails/missing-visibility.trail.ts +22 -0
  137. package/src/trails/no-destructured-compose.trail.ts +44 -0
  138. package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
  139. package/src/trails/no-direct-implementation-call.trail.ts +16 -0
  140. package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
  141. package/src/trails/no-legacy-layer-imports.trail.ts +41 -0
  142. package/src/trails/no-native-error-result.trail.ts +18 -0
  143. package/src/trails/no-redundant-result-error-wrap.trail.ts +55 -0
  144. package/src/trails/no-retired-cross-vocabulary.trail.ts +42 -0
  145. package/src/trails/no-sync-result-assumption.trail.ts +19 -0
  146. package/src/trails/no-throw-in-detour-recover.trail.ts +24 -0
  147. package/src/trails/no-throw-in-implementation.trail.ts +20 -0
  148. package/src/trails/no-top-level-surface.trail.ts +43 -0
  149. package/src/trails/on-references-exist.trail.ts +21 -0
  150. package/src/trails/orphaned-signal.trail.ts +36 -0
  151. package/src/trails/owner-render-parity.trail.ts +26 -0
  152. package/src/trails/pending-force.trail.ts +21 -0
  153. package/src/trails/permit-governance.trail.ts +51 -0
  154. package/src/trails/prefer-schema-inference.trail.ts +21 -0
  155. package/src/trails/public-export-example-coverage.trail.ts +16 -0
  156. package/src/trails/public-internal-deep-imports.trail.ts +94 -0
  157. package/src/trails/public-output-schema.trail.ts +55 -0
  158. package/src/trails/public-union-output-discriminants.trail.ts +33 -0
  159. package/src/trails/read-intent-fires.trail.ts +20 -0
  160. package/src/trails/reference-exists.trail.ts +25 -0
  161. package/src/trails/resolved-import-boundary.trail.ts +109 -0
  162. package/src/trails/resource-declarations.trail.ts +25 -0
  163. package/src/trails/resource-exists.trail.ts +27 -0
  164. package/src/trails/resource-id-grammar.trail.ts +39 -0
  165. package/src/trails/resource-mock-coverage.trail.ts +40 -0
  166. package/src/trails/run.ts +205 -0
  167. package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
  168. package/src/trails/schema.ts +295 -0
  169. package/src/trails/signal-graph-coaching.trail.ts +77 -0
  170. package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
  171. package/src/trails/surface-overlay-coherence.trail.ts +24 -0
  172. package/src/trails/surface-trailhead-coherence.trail.ts +25 -0
  173. package/src/trails/topo.ts +6 -0
  174. package/src/trails/trail-fork-coaching.trail.ts +42 -0
  175. package/src/trails/trailhead-override-divergence.trail.ts +47 -0
  176. package/src/trails/unmaterialized-activation-source.trail.ts +72 -0
  177. package/src/trails/unreachable-detour-shadowing.trail.ts +45 -0
  178. package/src/trails/valid-describe-refs.trail.ts +18 -0
  179. package/src/trails/valid-detour-contract.trail.ts +71 -0
  180. package/src/trails/version-gap.trail.ts +35 -0
  181. package/src/trails/version-pinned-compose.trail.ts +23 -0
  182. package/src/trails/version-without-examples.trail.ts +38 -0
  183. package/src/trails/warden-export-symmetry.trail.ts +16 -0
  184. package/src/trails/warden-rules-use-ast.trail.ts +64 -0
  185. package/src/trails/webhook-route-collision.trail.ts +50 -0
  186. package/src/trails/workspace-lock-ownership.trail.ts +16 -0
  187. package/src/trails/wrap-rule.ts +269 -0
  188. package/src/workspaces.ts +264 -0
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # @ontrails/warden
2
+
3
+ AST-based code convention rules for Trails. Built-in lint rules catch contract violations at development time, alongside lock drift detection and CI formatters.
4
+
5
+ Structural checks (compose target existence, declared resource existence, recursive composition, example schema validation) live in `validateTopo()` from `@ontrails/core`. Warden handles the code-level rules that need AST analysis.
6
+
7
+ For rule-home boundaries and authoring doctrine, see the [Warden guide](../../docs/warden.md) and [Warden Rules](../../docs/contributing/warden-rules.md).
8
+
9
+ ## Usage
10
+
11
+ From the Trails CLI:
12
+
13
+ ```bash
14
+ bunx trails warden # Run all checks
15
+ ```
16
+
17
+ Or programmatically:
18
+
19
+ ```typescript
20
+ import { runWarden, formatWardenReport } from '@ontrails/warden';
21
+
22
+ const report = await runWarden({ topo: graph });
23
+ console.log(formatWardenReport(report));
24
+ ```
25
+
26
+ ## Rules
27
+
28
+ Built-in rules are registered in `wardenRules` and `wardenTopoRules`; use those registries or `wardenTopo.ids()` for the current rule list instead of copying a static table into docs.
29
+
30
+ Rules cover several families:
31
+
32
+ - implementation and `Result` contract checks
33
+ - compose, fire, resource, and detour declaration drift
34
+ - draft-state containment
35
+ - source-static guardrails such as surface-type leakage
36
+ - topo-aware checks that need the resolved graph or resource mock shape
37
+
38
+ When adding or auditing rules, follow [Warden Rules](../../docs/contributing/warden-rules.md): name the invariant, import owner-held framework data, choose the narrowest Warden tier, and collapse families only when the data model, traversal, and diagnostic shape are shared.
39
+
40
+ ## Project-local rules
41
+
42
+ Projects can carry local Warden rules in `.trails/rules.ts` or direct `.trails/rules/*.ts` files. `runWarden()` and `trails warden` load those files by default for lint runs, then run those rules alongside the built-in registries. Drift-only runs do not import project-local rule modules. Embedders that need only built-in or explicitly provided rules can pass `projectRules: false`.
43
+
44
+ Warden does not recursively discover nested `.trails/rules` files. Use nested files as private helpers and re-export from a direct entrypoint when a local rule grows. Rule ids must be unique across every project-local module. The retired `trails/warden/rules` location reports a migration diagnostic instead of loading.
45
+
46
+ Warden uses the shared Trails project-root resolver when a caller does not pass `rootDir` or `--root-dir`, so commands launched from nested directories still load the nearest root `trails.config.*` and its `.trails/rules*` files. An explicit root always wins over discovery.
47
+
48
+ This is the right home for repo-specific migration checks or governance that has not earned a place in `@ontrails/warden` itself.
49
+
50
+ ## Scope
51
+
52
+ Use `warden.scope.exclude` in `trails.config.*` to keep generated, scratch, or local planning paths outside Warden governance while leaving durable project paths in scope.
53
+
54
+ ```json
55
+ {
56
+ "warden": {
57
+ "scope": {
58
+ "exclude": [".scratch/**", ".agents/notes/**"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ For a single run, pass one or more root-relative globs:
65
+
66
+ ```bash
67
+ warden --scope-exclude '.scratch/**' --scope-exclude '.agents/notes/**'
68
+ ```
69
+
70
+ Scope is a governance boundary, not a migration scan boundary. Regrade uses its own `scope.exclude` / `--exclude` controls for migration plans.
71
+
72
+ A rule module may export `rule`, `rules`, `sourceRule`, `sourceRules`, `topoRule`, or `topoRules`. Rules without explicit metadata receive default repo-local source-static or topo-aware metadata so short migration rules can run without extra ceremony. Project-aware source rules that provide `checkWithContext()` default to repo-local project-static metadata.
73
+
74
+ ```typescript
75
+ export const rule = {
76
+ name: 'local-contract-check',
77
+ severity: 'error',
78
+ description: 'Local contract examples keep their migration marker.',
79
+ check(sourceCode, filePath) {
80
+ return sourceCode.includes('deprecatedMarker')
81
+ ? [
82
+ {
83
+ filePath,
84
+ line: 1,
85
+ message: 'Replace deprecatedMarker before release.',
86
+ rule: 'local-contract-check',
87
+ severity: 'error',
88
+ },
89
+ ]
90
+ : [];
91
+ },
92
+ };
93
+ ```
94
+
95
+ ## Drift detection
96
+
97
+ Warden integrates with `@ontrails/topography` to detect when the topo has changed without updating the lock file:
98
+
99
+ ```typescript
100
+ import { checkDrift } from '@ontrails/warden';
101
+
102
+ const drift = await checkDrift(process.cwd(), graph);
103
+ if (drift.stale) {
104
+ console.log('lock file is stale -- regenerate with `trails compile`');
105
+ }
106
+ ```
107
+
108
+ ## CI integration
109
+
110
+ Add to lefthook for pre-push enforcement:
111
+
112
+ ```yaml
113
+ pre-push:
114
+ commands:
115
+ warden:
116
+ run: bunx trails warden
117
+ tags: governance
118
+ ```
119
+
120
+ CI formatters for structured output:
121
+
122
+ ```typescript
123
+ import {
124
+ formatGitHubAnnotations,
125
+ formatJson,
126
+ formatSummary,
127
+ } from '@ontrails/warden';
128
+ ```
129
+
130
+ Reusable source-code parser helpers now live in `@ontrails/source`:
131
+
132
+ ```typescript
133
+ import { findStringLiterals, parse, walk } from '@ontrails/source';
134
+ ```
135
+
136
+ ## Trail-based API
137
+
138
+ Every built-in warden rule is also available as a composable trail. This makes rules queryable, testable, and invocable through any Trails surface.
139
+
140
+ ```typescript
141
+ import {
142
+ runTopoAwareWardenTrails,
143
+ runWardenTrails,
144
+ wardenTopo,
145
+ } from '@ontrails/warden';
146
+
147
+ // Inspect the warden rule trails
148
+ console.log(wardenTopo.ids()); // ['warden.rule.no-throw-in-implementation', ...]
149
+
150
+ // Run all rule trails against a source file
151
+ const diagnostics = await runWardenTrails(filePath, sourceCode, {
152
+ knownTrailIds: myApp.ids(),
153
+ knownResourceIds: myApp.resourceIds(),
154
+ });
155
+
156
+ // Run built-in topo-aware rule trails once against the resolved graph
157
+ const topoDiagnostics = await runTopoAwareWardenTrails(myApp);
158
+ ```
159
+
160
+ To wrap a custom rule as a trail, import `wrapRule` from the root package entrypoint:
161
+
162
+ ```typescript
163
+ import { wrapRule } from '@ontrails/warden';
164
+ ```
165
+
166
+ This is the same factory used internally to build all built-in rule trails.
167
+
168
+ ## API
169
+
170
+ | Export | What it does |
171
+ | --- | --- |
172
+ | `runWarden(options?)` | Run all rules and drift checks, return a report |
173
+ | `formatWardenReport(report)` | Human-readable report |
174
+ | `checkDrift(rootDir, topo?, options?)` | Check if the lock file matches the current topo; pass `{ overlays }` from the app module so the comparison graph carries the overlay content compile embeds. Stale results name `driftedOverlayNamespaces` when overlays diverge |
175
+ | `wardenRules` | Registry of all built-in rules |
176
+ | `builtinWardenRuleMetadata` | Tier, scope, lifecycle, and invariant metadata for built-in rules |
177
+ | `getWardenRuleMetadata(ruleOrName)` | Resolve inline or built-in metadata for a Warden rule |
178
+ | `listWardenRuleMetadata()` | List built-in rule metadata entries |
179
+ | `wardenTopo` | `Topo` of all built-in rule trails (one per rule) |
180
+ | `runWardenTrails(filePath, sourceCode, options?)` | Dispatch file-scoped rule trails for a file, collect diagnostics |
181
+ | `runTopoAwareWardenTrails(topo)` | Dispatch built-in topo-aware rule trails once for a resolved topo |
182
+ | `loadProjectWardenRules(rootDir)` | Load rule modules from `.trails/rules.ts` or direct `.trails/rules/*.ts` children |
183
+ | `formatGitHubAnnotations(report)` | GitHub Actions annotation format |
184
+ | `formatJson(report)` | Machine-readable JSON |
185
+ | `formatSummary(report)` | Compact summary line |
186
+ | `wrapRule(rule)` | Wrap a custom rule as a trail (same factory used for all built-in rule trails) |
187
+
188
+ Source-code parser helpers are owned by `@ontrails/source`, not the Warden root runtime barrel.
189
+
190
+ `runWarden({ tier })` can narrow a run to `source-static`, `project-static`, `topo-aware`, `drift`, or `advisory`. Omit `tier` for the default full run.
191
+
192
+ See the [API Reference](../../docs/api-reference.md) for the full list.
193
+
194
+ ## Installation
195
+
196
+ These commands target stable `0.2.0`. Run them after that version is published to npm.
197
+
198
+ ```bash
199
+ bun add --exact -d @ontrails/warden@0.2.0
200
+ ```
package/bin/warden.ts ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bun
2
+ /* oxlint-disable eslint-plugin-jest/require-hook -- CLI bin entrypoints execute at module scope */
3
+
4
+ import { runWardenCommand } from '../src/command.js';
5
+
6
+ const HELP = `Usage: warden [options]
7
+
8
+ Run Trails Warden governance checks.
9
+
10
+ Options:
11
+ --ci Apply CI defaults
12
+ --pre-push Apply pre-push defaults
13
+ --apps, -a <names> Comma-delimited Trails app names
14
+ --config-path <path> Path to trails.config.* file
15
+ --root-dir <path> Project root to inspect
16
+ --fix Apply safe source fixes
17
+ --adapter-check Include shared adapter authoring diagnostics
18
+ --depth <value> source, project, topo, or all
19
+ --fail-on <value> error or warning
20
+ --format <value> summary, github, or json
21
+ --drafts <value> include, exclude, or only
22
+ --scope-exclude <glob> Root-relative path glob Warden should not govern
23
+ --lock <value> auto, cached, refresh, or skip
24
+ --no-lock-mutation Do not write lock artifacts
25
+ --strict Fail on warnings
26
+ -h, --help Display help for command
27
+ `;
28
+
29
+ const args = Bun.argv.slice(2);
30
+ if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
31
+ console.log(HELP);
32
+ process.exit(0);
33
+ }
34
+
35
+ const env = { ...process.env } as Record<string, string | undefined>;
36
+ const result = await runWardenCommand({
37
+ args,
38
+ cwd: process.cwd(),
39
+ env,
40
+ });
41
+
42
+ if (result.output.length > 0) {
43
+ console.log(result.output);
44
+ }
45
+
46
+ const summaryPath = env['GITHUB_STEP_SUMMARY'];
47
+ if (result.writeStepSummary && summaryPath !== undefined) {
48
+ await Bun.write(summaryPath, result.summary);
49
+ }
50
+
51
+ process.exit(result.exitCode);
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@ontrails/warden",
3
+ "version": "0.2.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/outfitter-dev/trails.git",
7
+ "directory": "packages/warden"
8
+ },
9
+ "bin": {
10
+ "warden": "./bin/warden.ts"
11
+ },
12
+ "files": [
13
+ "bin/**/*.ts",
14
+ "src/**/*.ts",
15
+ "!src/**/__tests__/**",
16
+ "!src/**/*.test.ts",
17
+ "!src/**/*.test-d.ts",
18
+ "README.md",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "type": "module",
22
+ "exports": {
23
+ ".": "./src/index.ts",
24
+ "./resolve": "./src/resolve.ts",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -b",
29
+ "test": "bun test",
30
+ "typecheck": "tsc --noEmit",
31
+ "lint": "oxlint ./src",
32
+ "clean": "rm -rf dist *.tsbuildinfo"
33
+ },
34
+ "dependencies": {
35
+ "@ontrails/adapter-kit": "^0.2.0",
36
+ "@ontrails/cli": "^0.2.0",
37
+ "@ontrails/config": "^0.2.0",
38
+ "@ontrails/permits": "^0.2.0",
39
+ "@ontrails/source": "^0.2.0",
40
+ "@ontrails/store": "^0.2.0",
41
+ "oxc-resolver": "11.19.1",
42
+ "zod": "^4.3.5"
43
+ },
44
+ "devDependencies": {
45
+ "@ontrails/testing": "^0.2.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@ontrails/core": "^0.2.0",
49
+ "@ontrails/topography": "^0.2.0"
50
+ }
51
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Warden rendering for shared adapter readiness checks.
3
+ *
4
+ * Adapter facts stay in @ontrails/adapter-kit. Warden only maps those facts
5
+ * into governance diagnostics and severity.
6
+ */
7
+
8
+ import { checkAdapters } from '@ontrails/adapter-kit';
9
+ import type { AdapterCheckDiagnostic } from '@ontrails/adapter-kit';
10
+ import { existsSync, readFileSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+
13
+ import type { WardenDiagnostic } from './rules/types.js';
14
+
15
+ export const adapterCheckRuleName = 'adapter-check';
16
+ const adapterCheckRootCode = 'adapter-check-root';
17
+
18
+ const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
19
+ typeof value === 'object' && value !== null && !Array.isArray(value);
20
+
21
+ const toWardenDiagnostic = (
22
+ diagnostic: AdapterCheckDiagnostic
23
+ ): WardenDiagnostic => ({
24
+ code: diagnostic.code,
25
+ filePath: diagnostic.packageJsonPath,
26
+ line: 1,
27
+ message: diagnostic.message,
28
+ rule: adapterCheckRuleName,
29
+ severity: 'warn',
30
+ });
31
+
32
+ const toRootDiagnostic = (
33
+ message: string,
34
+ filePath: string
35
+ ): WardenDiagnostic => ({
36
+ code: adapterCheckRootCode,
37
+ filePath,
38
+ line: 1,
39
+ message,
40
+ rule: adapterCheckRuleName,
41
+ severity: 'error',
42
+ });
43
+
44
+ const workspacePatternsFromManifest = (
45
+ manifest: Readonly<Record<string, unknown>>
46
+ ): readonly string[] => {
47
+ const { workspaces } = manifest;
48
+ if (Array.isArray(workspaces)) {
49
+ return workspaces.filter(
50
+ (pattern): pattern is string => typeof pattern === 'string'
51
+ );
52
+ }
53
+
54
+ const packages = isRecord(workspaces) ? workspaces['packages'] : undefined;
55
+ return Array.isArray(packages)
56
+ ? packages.filter(
57
+ (pattern): pattern is string => typeof pattern === 'string'
58
+ )
59
+ : [];
60
+ };
61
+
62
+ const validateAdapterCheckRoot = (
63
+ rootDir: string
64
+ ): readonly WardenDiagnostic[] => {
65
+ if (!existsSync(rootDir)) {
66
+ return [
67
+ toRootDiagnostic(
68
+ `adapter.check rootDir does not exist: "${rootDir}"`,
69
+ rootDir
70
+ ),
71
+ ];
72
+ }
73
+
74
+ if (!statSync(rootDir).isDirectory()) {
75
+ return [
76
+ toRootDiagnostic(
77
+ `adapter.check rootDir must be a directory: "${rootDir}"`,
78
+ rootDir
79
+ ),
80
+ ];
81
+ }
82
+
83
+ const packageJsonPath = join(rootDir, 'package.json');
84
+ if (!existsSync(packageJsonPath)) {
85
+ return [
86
+ toRootDiagnostic(
87
+ `adapter.check rootDir must contain a package.json workspace manifest: "${packageJsonPath}"`,
88
+ packageJsonPath
89
+ ),
90
+ ];
91
+ }
92
+
93
+ let manifest: unknown;
94
+ try {
95
+ manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
96
+ } catch (error) {
97
+ const reason = error instanceof Error ? `: ${error.message}` : '';
98
+ return [
99
+ toRootDiagnostic(
100
+ `adapter.check could not read root package.json: "${packageJsonPath}"${reason}`,
101
+ packageJsonPath
102
+ ),
103
+ ];
104
+ }
105
+
106
+ if (!isRecord(manifest)) {
107
+ return [
108
+ toRootDiagnostic(
109
+ `adapter.check root package.json must contain a JSON object: "${packageJsonPath}"`,
110
+ packageJsonPath
111
+ ),
112
+ ];
113
+ }
114
+
115
+ if (workspacePatternsFromManifest(manifest).length === 0) {
116
+ return [
117
+ toRootDiagnostic(
118
+ `adapter.check root package.json must declare workspace packages: "${packageJsonPath}"`,
119
+ packageJsonPath
120
+ ),
121
+ ];
122
+ }
123
+
124
+ return [];
125
+ };
126
+
127
+ export const runWardenAdapterChecks = (
128
+ rootDir: string
129
+ ): readonly WardenDiagnostic[] => {
130
+ const rootDiagnostics = validateAdapterCheckRoot(rootDir);
131
+ if (rootDiagnostics.length > 0) {
132
+ return rootDiagnostics;
133
+ }
134
+
135
+ return checkAdapters(rootDir).diagnostics.map(toWardenDiagnostic);
136
+ };