@ontrails/warden 1.0.0-beta.15 → 1.0.0-beta.17
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/CHANGELOG.md +144 -1
- package/README.md +64 -30
- package/bin/warden.ts +22 -0
- package/package.json +25 -8
- package/src/ast.ts +28 -0
- package/src/cli.ts +740 -46
- package/src/command.ts +927 -0
- package/src/config.ts +184 -0
- package/src/drift.ts +76 -34
- package/src/formatters.ts +54 -7
- package/src/guide.ts +234 -0
- package/src/index.ts +109 -14
- package/src/project-context.ts +163 -0
- package/src/resolve.ts +530 -0
- package/src/rules/activation-orphan.ts +97 -0
- package/src/rules/ast.ts +233 -31
- package/src/rules/draft-visible-debt.ts +2 -2
- package/src/rules/error-mapping-completeness.ts +24 -9
- package/src/rules/fires-declarations.ts +201 -52
- package/src/rules/incomplete-accessor-for-standard-op.ts +8 -51
- package/src/rules/incomplete-crud.ts +8 -7
- package/src/rules/index.ts +67 -3
- package/src/rules/intent-propagation.ts +6 -21
- package/src/rules/layer-field-name-drift.ts +96 -0
- package/src/rules/metadata.ts +508 -0
- package/src/rules/missing-visibility.ts +1 -1
- package/src/rules/no-dev-permit-in-source.ts +99 -0
- package/src/rules/no-legacy-layer-imports.ts +193 -0
- package/src/rules/no-native-error-result.ts +111 -0
- package/src/rules/no-sync-result-assumption.ts +5 -8
- package/src/rules/orphaned-signal.ts +1 -1
- package/src/rules/owner-projection-parity.ts +146 -0
- package/src/rules/public-internal-deep-imports.ts +517 -0
- package/src/rules/public-output-schema.ts +29 -0
- package/src/rules/public-union-output-discriminants.ts +150 -0
- package/src/rules/read-intent-fires.ts +187 -0
- package/src/rules/registry-names.ts +32 -2
- package/src/rules/resolved-import-boundary.ts +146 -0
- package/src/rules/scan.ts +0 -26
- package/src/rules/scheduled-destroy-intent.ts +44 -0
- package/src/rules/signal-graph-coaching.ts +191 -0
- package/src/rules/static-resource-accessor-preference.ts +657 -0
- package/src/rules/types.ts +132 -5
- package/src/rules/unmaterialized-activation-source.ts +84 -0
- package/src/rules/unreachable-detour-shadowing.ts +2 -33
- package/src/rules/webhook-route-collision.ts +243 -0
- package/src/trails/activation-orphan.trail.ts +81 -0
- package/src/trails/error-mapping-completeness.trail.ts +4 -4
- package/src/trails/fires-declarations.trail.ts +4 -3
- package/src/trails/index.ts +16 -1
- package/src/trails/layer-field-name-drift.trail.ts +39 -0
- package/src/trails/no-dev-permit-in-source.trail.ts +16 -0
- package/src/trails/no-legacy-layer-imports.trail.ts +35 -0
- package/src/trails/no-native-error-result.trail.ts +18 -0
- package/src/trails/orphaned-signal.trail.ts +1 -1
- package/src/trails/owner-projection-parity.trail.ts +26 -0
- package/src/trails/public-internal-deep-imports.trail.ts +94 -0
- package/src/trails/public-output-schema.trail.ts +55 -0
- package/src/trails/public-union-output-discriminants.trail.ts +33 -0
- package/src/trails/read-intent-fires.trail.ts +20 -0
- package/src/trails/resolved-import-boundary.trail.ts +109 -0
- package/src/trails/run.ts +14 -2
- package/src/trails/scheduled-destroy-intent.trail.ts +56 -0
- package/src/trails/schema.ts +57 -1
- package/src/trails/signal-graph-coaching.trail.ts +74 -0
- package/src/trails/static-resource-accessor-preference.trail.ts +25 -0
- package/src/trails/unmaterialized-activation-source.trail.ts +69 -0
- package/src/trails/webhook-route-collision.trail.ts +50 -0
- package/src/trails/wrap-rule.ts +30 -3
- package/src/workspaces.ts +238 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/cli.d.ts +0 -63
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -436
- package/dist/cli.js.map +0 -1
- package/dist/draft.d.ts +0 -5
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -16
- package/dist/draft.js.map +0 -1
- package/dist/drift.d.ts +0 -29
- package/dist/drift.d.ts.map +0 -1
- package/dist/drift.js +0 -61
- package/dist/drift.js.map +0 -1
- package/dist/formatters.d.ts +0 -30
- package/dist/formatters.d.ts.map +0 -1
- package/dist/formatters.js +0 -98
- package/dist/formatters.js.map +0 -1
- package/dist/index.d.ts +0 -24
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -25
- package/dist/index.js.map +0 -1
- package/dist/rules/ast.d.ts +0 -480
- package/dist/rules/ast.d.ts.map +0 -1
- package/dist/rules/ast.js +0 -2086
- package/dist/rules/ast.js.map +0 -1
- package/dist/rules/circular-refs.d.ts +0 -6
- package/dist/rules/circular-refs.d.ts.map +0 -1
- package/dist/rules/circular-refs.js +0 -83
- package/dist/rules/circular-refs.js.map +0 -1
- package/dist/rules/context-no-surface-types.d.ts +0 -12
- package/dist/rules/context-no-surface-types.d.ts.map +0 -1
- package/dist/rules/context-no-surface-types.js +0 -152
- package/dist/rules/context-no-surface-types.js.map +0 -1
- package/dist/rules/context-no-trailhead-types.d.ts +0 -12
- package/dist/rules/context-no-trailhead-types.d.ts.map +0 -1
- package/dist/rules/context-no-trailhead-types.js +0 -96
- package/dist/rules/context-no-trailhead-types.js.map +0 -1
- package/dist/rules/contour-exists.d.ts +0 -7
- package/dist/rules/contour-exists.d.ts.map +0 -1
- package/dist/rules/contour-exists.js +0 -113
- package/dist/rules/contour-exists.js.map +0 -1
- package/dist/rules/contour-ids.d.ts +0 -10
- package/dist/rules/contour-ids.d.ts.map +0 -1
- package/dist/rules/contour-ids.js +0 -12
- package/dist/rules/contour-ids.js.map +0 -1
- package/dist/rules/cross-declarations.d.ts +0 -13
- package/dist/rules/cross-declarations.d.ts.map +0 -1
- package/dist/rules/cross-declarations.js +0 -378
- package/dist/rules/cross-declarations.js.map +0 -1
- package/dist/rules/dead-internal-trail.d.ts +0 -3
- package/dist/rules/dead-internal-trail.d.ts.map +0 -1
- package/dist/rules/dead-internal-trail.js +0 -80
- package/dist/rules/dead-internal-trail.js.map +0 -1
- package/dist/rules/draft-file-marking.d.ts +0 -6
- package/dist/rules/draft-file-marking.d.ts.map +0 -1
- package/dist/rules/draft-file-marking.js +0 -87
- package/dist/rules/draft-file-marking.js.map +0 -1
- package/dist/rules/draft-visible-debt.d.ts +0 -12
- package/dist/rules/draft-visible-debt.d.ts.map +0 -1
- package/dist/rules/draft-visible-debt.js +0 -50
- package/dist/rules/draft-visible-debt.js.map +0 -1
- package/dist/rules/error-mapping-completeness.d.ts +0 -13
- package/dist/rules/error-mapping-completeness.d.ts.map +0 -1
- package/dist/rules/error-mapping-completeness.js +0 -160
- package/dist/rules/error-mapping-completeness.js.map +0 -1
- package/dist/rules/example-valid.d.ts +0 -6
- package/dist/rules/example-valid.d.ts.map +0 -1
- package/dist/rules/example-valid.js +0 -203
- package/dist/rules/example-valid.js.map +0 -1
- package/dist/rules/fires-declarations.d.ts +0 -16
- package/dist/rules/fires-declarations.d.ts.map +0 -1
- package/dist/rules/fires-declarations.js +0 -444
- package/dist/rules/fires-declarations.js.map +0 -1
- package/dist/rules/implementation-returns-result.d.ts +0 -22
- package/dist/rules/implementation-returns-result.d.ts.map +0 -1
- package/dist/rules/implementation-returns-result.js +0 -839
- package/dist/rules/implementation-returns-result.js.map +0 -1
- package/dist/rules/incomplete-accessor-for-standard-op.d.ts +0 -30
- package/dist/rules/incomplete-accessor-for-standard-op.d.ts.map +0 -1
- package/dist/rules/incomplete-accessor-for-standard-op.js +0 -226
- package/dist/rules/incomplete-accessor-for-standard-op.js.map +0 -1
- package/dist/rules/incomplete-crud.d.ts +0 -21
- package/dist/rules/incomplete-crud.d.ts.map +0 -1
- package/dist/rules/incomplete-crud.js +0 -368
- package/dist/rules/incomplete-crud.js.map +0 -1
- package/dist/rules/index.d.ts +0 -51
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js +0 -119
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/intent-propagation.d.ts +0 -3
- package/dist/rules/intent-propagation.d.ts.map +0 -1
- package/dist/rules/intent-propagation.js +0 -57
- package/dist/rules/intent-propagation.js.map +0 -1
- package/dist/rules/missing-reconcile.d.ts +0 -3
- package/dist/rules/missing-reconcile.d.ts.map +0 -1
- package/dist/rules/missing-reconcile.js +0 -44
- package/dist/rules/missing-reconcile.js.map +0 -1
- package/dist/rules/missing-visibility.d.ts +0 -3
- package/dist/rules/missing-visibility.d.ts.map +0 -1
- package/dist/rules/missing-visibility.js +0 -63
- package/dist/rules/missing-visibility.js.map +0 -1
- package/dist/rules/no-direct-impl-in-route.d.ts +0 -12
- package/dist/rules/no-direct-impl-in-route.d.ts.map +0 -1
- package/dist/rules/no-direct-impl-in-route.js +0 -44
- package/dist/rules/no-direct-impl-in-route.js.map +0 -1
- package/dist/rules/no-direct-implementation-call.d.ts +0 -12
- package/dist/rules/no-direct-implementation-call.d.ts.map +0 -1
- package/dist/rules/no-direct-implementation-call.js +0 -39
- package/dist/rules/no-direct-implementation-call.js.map +0 -1
- package/dist/rules/no-sync-result-assumption.d.ts +0 -6
- package/dist/rules/no-sync-result-assumption.d.ts.map +0 -1
- package/dist/rules/no-sync-result-assumption.js +0 -907
- package/dist/rules/no-sync-result-assumption.js.map +0 -1
- package/dist/rules/no-throw-in-detour-recover.d.ts +0 -3
- package/dist/rules/no-throw-in-detour-recover.d.ts.map +0 -1
- package/dist/rules/no-throw-in-detour-recover.js +0 -147
- package/dist/rules/no-throw-in-detour-recover.js.map +0 -1
- package/dist/rules/no-throw-in-detour-target.d.ts +0 -15
- package/dist/rules/no-throw-in-detour-target.d.ts.map +0 -1
- package/dist/rules/no-throw-in-detour-target.js +0 -90
- package/dist/rules/no-throw-in-detour-target.js.map +0 -1
- package/dist/rules/no-throw-in-implementation.d.ts +0 -11
- package/dist/rules/no-throw-in-implementation.d.ts.map +0 -1
- package/dist/rules/no-throw-in-implementation.js +0 -36
- package/dist/rules/no-throw-in-implementation.js.map +0 -1
- package/dist/rules/on-references-exist.d.ts +0 -14
- package/dist/rules/on-references-exist.d.ts.map +0 -1
- package/dist/rules/on-references-exist.js +0 -109
- package/dist/rules/on-references-exist.js.map +0 -1
- package/dist/rules/orphaned-signal.d.ts +0 -3
- package/dist/rules/orphaned-signal.d.ts.map +0 -1
- package/dist/rules/orphaned-signal.js +0 -67
- package/dist/rules/orphaned-signal.js.map +0 -1
- package/dist/rules/permit-governance.d.ts +0 -3
- package/dist/rules/permit-governance.d.ts.map +0 -1
- package/dist/rules/permit-governance.js +0 -15
- package/dist/rules/permit-governance.js.map +0 -1
- package/dist/rules/prefer-schema-inference.d.ts +0 -7
- package/dist/rules/prefer-schema-inference.d.ts.map +0 -1
- package/dist/rules/prefer-schema-inference.js +0 -86
- package/dist/rules/prefer-schema-inference.js.map +0 -1
- package/dist/rules/reference-exists.d.ts +0 -6
- package/dist/rules/reference-exists.d.ts.map +0 -1
- package/dist/rules/reference-exists.js +0 -47
- package/dist/rules/reference-exists.js.map +0 -1
- package/dist/rules/registry-names.d.ts +0 -8
- package/dist/rules/registry-names.d.ts.map +0 -1
- package/dist/rules/registry-names.js +0 -83
- package/dist/rules/registry-names.js.map +0 -1
- package/dist/rules/resource-declarations.d.ts +0 -14
- package/dist/rules/resource-declarations.d.ts.map +0 -1
- package/dist/rules/resource-declarations.js +0 -413
- package/dist/rules/resource-declarations.js.map +0 -1
- package/dist/rules/resource-exists.d.ts +0 -6
- package/dist/rules/resource-exists.d.ts.map +0 -1
- package/dist/rules/resource-exists.js +0 -90
- package/dist/rules/resource-exists.js.map +0 -1
- package/dist/rules/resource-id-grammar.d.ts +0 -3
- package/dist/rules/resource-id-grammar.d.ts.map +0 -1
- package/dist/rules/resource-id-grammar.js +0 -39
- package/dist/rules/resource-id-grammar.js.map +0 -1
- package/dist/rules/scan.d.ts +0 -8
- package/dist/rules/scan.d.ts.map +0 -1
- package/dist/rules/scan.js +0 -32
- package/dist/rules/scan.js.map +0 -1
- package/dist/rules/specs.d.ts +0 -29
- package/dist/rules/specs.d.ts.map +0 -1
- package/dist/rules/specs.js +0 -196
- package/dist/rules/specs.js.map +0 -1
- package/dist/rules/structure.d.ts +0 -13
- package/dist/rules/structure.d.ts.map +0 -1
- package/dist/rules/structure.js +0 -142
- package/dist/rules/structure.js.map +0 -1
- package/dist/rules/types.d.ts +0 -103
- package/dist/rules/types.d.ts.map +0 -1
- package/dist/rules/types.js +0 -2
- package/dist/rules/types.js.map +0 -1
- package/dist/rules/unreachable-detour-shadowing.d.ts +0 -3
- package/dist/rules/unreachable-detour-shadowing.d.ts.map +0 -1
- package/dist/rules/unreachable-detour-shadowing.js +0 -202
- package/dist/rules/unreachable-detour-shadowing.js.map +0 -1
- package/dist/rules/valid-describe-refs.d.ts +0 -7
- package/dist/rules/valid-describe-refs.d.ts.map +0 -1
- package/dist/rules/valid-describe-refs.js +0 -167
- package/dist/rules/valid-describe-refs.js.map +0 -1
- package/dist/rules/valid-detour-contract.d.ts +0 -3
- package/dist/rules/valid-detour-contract.d.ts.map +0 -1
- package/dist/rules/valid-detour-contract.js +0 -47
- package/dist/rules/valid-detour-contract.js.map +0 -1
- package/dist/rules/valid-detour-refs.d.ts +0 -6
- package/dist/rules/valid-detour-refs.d.ts.map +0 -1
- package/dist/rules/valid-detour-refs.js +0 -107
- package/dist/rules/valid-detour-refs.js.map +0 -1
- package/dist/rules/warden-export-symmetry.d.ts +0 -7
- package/dist/rules/warden-export-symmetry.d.ts.map +0 -1
- package/dist/rules/warden-export-symmetry.js +0 -352
- package/dist/rules/warden-export-symmetry.js.map +0 -1
- package/dist/rules/warden-rules-use-ast.d.ts +0 -17
- package/dist/rules/warden-rules-use-ast.d.ts.map +0 -1
- package/dist/rules/warden-rules-use-ast.js +0 -778
- package/dist/rules/warden-rules-use-ast.js.map +0 -1
- package/dist/trails/circular-refs.trail.d.ts +0 -24
- package/dist/trails/circular-refs.trail.d.ts.map +0 -1
- package/dist/trails/circular-refs.trail.js +0 -29
- package/dist/trails/circular-refs.trail.js.map +0 -1
- package/dist/trails/context-no-surface-types.trail.d.ts +0 -13
- package/dist/trails/context-no-surface-types.trail.d.ts.map +0 -1
- package/dist/trails/context-no-surface-types.trail.js +0 -21
- package/dist/trails/context-no-surface-types.trail.js.map +0 -1
- package/dist/trails/context-no-trailhead-types.trail.d.ts +0 -13
- package/dist/trails/context-no-trailhead-types.trail.d.ts.map +0 -1
- package/dist/trails/context-no-trailhead-types.trail.js +0 -21
- package/dist/trails/context-no-trailhead-types.trail.js.map +0 -1
- package/dist/trails/contour-exists.trail.d.ts +0 -24
- package/dist/trails/contour-exists.trail.d.ts.map +0 -1
- package/dist/trails/contour-exists.trail.js +0 -21
- package/dist/trails/contour-exists.trail.js.map +0 -1
- package/dist/trails/cross-declarations.trail.d.ts +0 -13
- package/dist/trails/cross-declarations.trail.d.ts.map +0 -1
- package/dist/trails/cross-declarations.trail.js +0 -22
- package/dist/trails/cross-declarations.trail.js.map +0 -1
- package/dist/trails/dead-internal-trail.trail.d.ts +0 -24
- package/dist/trails/dead-internal-trail.trail.d.ts.map +0 -1
- package/dist/trails/dead-internal-trail.trail.js +0 -26
- package/dist/trails/dead-internal-trail.trail.js.map +0 -1
- package/dist/trails/draft-file-marking.trail.d.ts +0 -13
- package/dist/trails/draft-file-marking.trail.d.ts.map +0 -1
- package/dist/trails/draft-file-marking.trail.js +0 -16
- package/dist/trails/draft-file-marking.trail.js.map +0 -1
- package/dist/trails/draft-visible-debt.trail.d.ts +0 -13
- package/dist/trails/draft-visible-debt.trail.d.ts.map +0 -1
- package/dist/trails/draft-visible-debt.trail.js +0 -16
- package/dist/trails/draft-visible-debt.trail.js.map +0 -1
- package/dist/trails/error-mapping-completeness.trail.d.ts +0 -13
- package/dist/trails/error-mapping-completeness.trail.d.ts.map +0 -1
- package/dist/trails/error-mapping-completeness.trail.js +0 -29
- package/dist/trails/error-mapping-completeness.trail.js.map +0 -1
- package/dist/trails/example-valid.trail.d.ts +0 -13
- package/dist/trails/example-valid.trail.d.ts.map +0 -1
- package/dist/trails/example-valid.trail.js +0 -25
- package/dist/trails/example-valid.trail.js.map +0 -1
- package/dist/trails/fires-declarations.trail.d.ts +0 -13
- package/dist/trails/fires-declarations.trail.d.ts.map +0 -1
- package/dist/trails/fires-declarations.trail.js +0 -22
- package/dist/trails/fires-declarations.trail.js.map +0 -1
- package/dist/trails/implementation-returns-result.trail.d.ts +0 -13
- package/dist/trails/implementation-returns-result.trail.d.ts.map +0 -1
- package/dist/trails/implementation-returns-result.trail.js +0 -20
- package/dist/trails/implementation-returns-result.trail.js.map +0 -1
- package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts +0 -12
- package/dist/trails/incomplete-accessor-for-standard-op.trail.d.ts.map +0 -1
- package/dist/trails/incomplete-accessor-for-standard-op.trail.js +0 -60
- package/dist/trails/incomplete-accessor-for-standard-op.trail.js.map +0 -1
- package/dist/trails/incomplete-crud.trail.d.ts +0 -24
- package/dist/trails/incomplete-crud.trail.d.ts.map +0 -1
- package/dist/trails/incomplete-crud.trail.js +0 -39
- package/dist/trails/incomplete-crud.trail.js.map +0 -1
- package/dist/trails/index.d.ts +0 -38
- package/dist/trails/index.d.ts.map +0 -1
- package/dist/trails/index.js +0 -37
- package/dist/trails/index.js.map +0 -1
- package/dist/trails/intent-propagation.trail.d.ts +0 -24
- package/dist/trails/intent-propagation.trail.d.ts.map +0 -1
- package/dist/trails/intent-propagation.trail.js +0 -30
- package/dist/trails/intent-propagation.trail.js.map +0 -1
- package/dist/trails/missing-reconcile.trail.d.ts +0 -24
- package/dist/trails/missing-reconcile.trail.d.ts.map +0 -1
- package/dist/trails/missing-reconcile.trail.js +0 -33
- package/dist/trails/missing-reconcile.trail.js.map +0 -1
- package/dist/trails/missing-visibility.trail.d.ts +0 -24
- package/dist/trails/missing-visibility.trail.d.ts.map +0 -1
- package/dist/trails/missing-visibility.trail.js +0 -22
- package/dist/trails/missing-visibility.trail.js.map +0 -1
- package/dist/trails/no-direct-impl-in-route.trail.d.ts +0 -13
- package/dist/trails/no-direct-impl-in-route.trail.d.ts.map +0 -1
- package/dist/trails/no-direct-impl-in-route.trail.js +0 -22
- package/dist/trails/no-direct-impl-in-route.trail.js.map +0 -1
- package/dist/trails/no-direct-implementation-call.trail.d.ts +0 -13
- package/dist/trails/no-direct-implementation-call.trail.d.ts.map +0 -1
- package/dist/trails/no-direct-implementation-call.trail.js +0 -16
- package/dist/trails/no-direct-implementation-call.trail.js.map +0 -1
- package/dist/trails/no-sync-result-assumption.trail.d.ts +0 -13
- package/dist/trails/no-sync-result-assumption.trail.d.ts.map +0 -1
- package/dist/trails/no-sync-result-assumption.trail.js +0 -19
- package/dist/trails/no-sync-result-assumption.trail.js.map +0 -1
- package/dist/trails/no-throw-in-detour-recover.trail.d.ts +0 -13
- package/dist/trails/no-throw-in-detour-recover.trail.d.ts.map +0 -1
- package/dist/trails/no-throw-in-detour-recover.trail.js +0 -24
- package/dist/trails/no-throw-in-detour-recover.trail.js.map +0 -1
- package/dist/trails/no-throw-in-detour-target.trail.d.ts +0 -25
- package/dist/trails/no-throw-in-detour-target.trail.d.ts.map +0 -1
- package/dist/trails/no-throw-in-detour-target.trail.js +0 -20
- package/dist/trails/no-throw-in-detour-target.trail.js.map +0 -1
- package/dist/trails/no-throw-in-implementation.trail.d.ts +0 -13
- package/dist/trails/no-throw-in-implementation.trail.d.ts.map +0 -1
- package/dist/trails/no-throw-in-implementation.trail.js +0 -20
- package/dist/trails/no-throw-in-implementation.trail.js.map +0 -1
- package/dist/trails/on-references-exist.trail.d.ts +0 -24
- package/dist/trails/on-references-exist.trail.d.ts.map +0 -1
- package/dist/trails/on-references-exist.trail.js +0 -21
- package/dist/trails/on-references-exist.trail.js.map +0 -1
- package/dist/trails/orphaned-signal.trail.d.ts +0 -24
- package/dist/trails/orphaned-signal.trail.d.ts.map +0 -1
- package/dist/trails/orphaned-signal.trail.js +0 -36
- package/dist/trails/orphaned-signal.trail.js.map +0 -1
- package/dist/trails/permit-governance.trail.d.ts +0 -12
- package/dist/trails/permit-governance.trail.d.ts.map +0 -1
- package/dist/trails/permit-governance.trail.js +0 -47
- package/dist/trails/permit-governance.trail.js.map +0 -1
- package/dist/trails/prefer-schema-inference.trail.d.ts +0 -13
- package/dist/trails/prefer-schema-inference.trail.d.ts.map +0 -1
- package/dist/trails/prefer-schema-inference.trail.js +0 -21
- package/dist/trails/prefer-schema-inference.trail.js.map +0 -1
- package/dist/trails/reference-exists.trail.d.ts +0 -24
- package/dist/trails/reference-exists.trail.d.ts.map +0 -1
- package/dist/trails/reference-exists.trail.js +0 -25
- package/dist/trails/reference-exists.trail.js.map +0 -1
- package/dist/trails/resource-declarations.trail.d.ts +0 -13
- package/dist/trails/resource-declarations.trail.d.ts.map +0 -1
- package/dist/trails/resource-declarations.trail.js +0 -25
- package/dist/trails/resource-declarations.trail.js.map +0 -1
- package/dist/trails/resource-exists.trail.d.ts +0 -24
- package/dist/trails/resource-exists.trail.d.ts.map +0 -1
- package/dist/trails/resource-exists.trail.js +0 -27
- package/dist/trails/resource-exists.trail.js.map +0 -1
- package/dist/trails/resource-id-grammar.trail.d.ts +0 -13
- package/dist/trails/resource-id-grammar.trail.d.ts.map +0 -1
- package/dist/trails/resource-id-grammar.trail.js +0 -38
- package/dist/trails/resource-id-grammar.trail.js.map +0 -1
- package/dist/trails/run.d.ts +0 -33
- package/dist/trails/run.d.ts.map +0 -1
- package/dist/trails/run.js +0 -83
- package/dist/trails/run.js.map +0 -1
- package/dist/trails/schema.d.ts +0 -78
- package/dist/trails/schema.d.ts.map +0 -1
- package/dist/trails/schema.js +0 -95
- package/dist/trails/schema.js.map +0 -1
- package/dist/trails/topo.d.ts +0 -3
- package/dist/trails/topo.d.ts.map +0 -1
- package/dist/trails/topo.js +0 -5
- package/dist/trails/topo.js.map +0 -1
- package/dist/trails/unreachable-detour-shadowing.trail.d.ts +0 -13
- package/dist/trails/unreachable-detour-shadowing.trail.d.ts.map +0 -1
- package/dist/trails/unreachable-detour-shadowing.trail.js +0 -44
- package/dist/trails/unreachable-detour-shadowing.trail.js.map +0 -1
- package/dist/trails/valid-describe-refs.trail.d.ts +0 -24
- package/dist/trails/valid-describe-refs.trail.d.ts.map +0 -1
- package/dist/trails/valid-describe-refs.trail.js +0 -18
- package/dist/trails/valid-describe-refs.trail.js.map +0 -1
- package/dist/trails/valid-detour-contract.trail.d.ts +0 -12
- package/dist/trails/valid-detour-contract.trail.d.ts.map +0 -1
- package/dist/trails/valid-detour-contract.trail.js +0 -66
- package/dist/trails/valid-detour-contract.trail.js.map +0 -1
- package/dist/trails/valid-detour-refs.trail.d.ts +0 -25
- package/dist/trails/valid-detour-refs.trail.d.ts.map +0 -1
- package/dist/trails/valid-detour-refs.trail.js +0 -24
- package/dist/trails/valid-detour-refs.trail.js.map +0 -1
- package/dist/trails/warden-export-symmetry.trail.d.ts +0 -13
- package/dist/trails/warden-export-symmetry.trail.d.ts.map +0 -1
- package/dist/trails/warden-export-symmetry.trail.js +0 -16
- package/dist/trails/warden-export-symmetry.trail.js.map +0 -1
- package/dist/trails/warden-rules-use-ast.trail.d.ts +0 -13
- package/dist/trails/warden-rules-use-ast.trail.d.ts.map +0 -1
- package/dist/trails/warden-rules-use-ast.trail.js +0 -41
- package/dist/trails/warden-rules-use-ast.trail.js.map +0 -1
- package/dist/trails/wrap-rule.d.ts +0 -43
- package/dist/trails/wrap-rule.d.ts.map +0 -1
- package/dist/trails/wrap-rule.js +0 -107
- package/dist/trails/wrap-rule.js.map +0 -1
- package/src/__tests__/ast.test.ts +0 -613
- package/src/__tests__/circular-refs.test.ts +0 -121
- package/src/__tests__/cli.test.ts +0 -526
- package/src/__tests__/contour-exists.test.ts +0 -203
- package/src/__tests__/cross-declarations.test.ts +0 -548
- package/src/__tests__/dead-internal-trail.test.ts +0 -81
- package/src/__tests__/draft-rules-context.test.ts +0 -150
- package/src/__tests__/drift.test.ts +0 -144
- package/src/__tests__/error-mapping-completeness.test.ts +0 -56
- package/src/__tests__/example-valid.test.ts +0 -101
- package/src/__tests__/fires-declarations-param-destructure.test.ts +0 -54
- package/src/__tests__/fires-declarations.test.ts +0 -652
- package/src/__tests__/formatters.test.ts +0 -157
- package/src/__tests__/implementation-returns-result.test.ts +0 -1143
- package/src/__tests__/incomplete-accessor-for-standard-op.test.ts +0 -337
- package/src/__tests__/incomplete-crud.test.ts +0 -498
- package/src/__tests__/intent-propagation.test.ts +0 -116
- package/src/__tests__/missing-reconcile.test.ts +0 -154
- package/src/__tests__/missing-visibility.test.ts +0 -108
- package/src/__tests__/no-direct-implementation-call.test.ts +0 -83
- package/src/__tests__/no-sync-result-assumption.test.ts +0 -916
- package/src/__tests__/no-throw-in-detour-recover.test.ts +0 -93
- package/src/__tests__/no-throw-in-implementation.test.ts +0 -88
- package/src/__tests__/on-references-exist.test.ts +0 -151
- package/src/__tests__/orphaned-signal.test.ts +0 -137
- package/src/__tests__/permit-governance.test.ts +0 -66
- package/src/__tests__/prefer-schema-inference.test.ts +0 -84
- package/src/__tests__/reference-exists.test.ts +0 -281
- package/src/__tests__/resource-declarations.test.ts +0 -448
- package/src/__tests__/resource-exists.test.ts +0 -122
- package/src/__tests__/resource-id-grammar.test.ts +0 -50
- package/src/__tests__/rules.test.ts +0 -167
- package/src/__tests__/topo-aware-rule.test.ts +0 -257
- package/src/__tests__/trails.test.ts +0 -19
- package/src/__tests__/unreachable-detour-shadowing.test.ts +0 -128
- package/src/__tests__/valid-describe-refs.test.ts +0 -243
- package/src/__tests__/valid-detour-contract.test.ts +0 -86
- package/src/__tests__/warden-export-symmetry.test.ts +0 -251
- package/src/__tests__/warden-rules-use-ast.test.ts +0 -468
- package/src/__tests__/wrap-rule.test.ts +0 -41
- package/src/rules/no-direct-impl-in-route.ts +0 -77
- package/src/trails/no-direct-impl-in-route.trail.ts +0 -22
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
package/src/cli.ts
CHANGED
|
@@ -10,8 +10,24 @@ import { resolve } from 'node:path';
|
|
|
10
10
|
import type { Topo } from '@ontrails/core';
|
|
11
11
|
import { getContourReferences } from '@ontrails/core';
|
|
12
12
|
|
|
13
|
+
import type {
|
|
14
|
+
EffectiveWardenConfig,
|
|
15
|
+
WardenConfigInput,
|
|
16
|
+
WardenConfigLayer,
|
|
17
|
+
WardenDepth,
|
|
18
|
+
WardenFailOn,
|
|
19
|
+
WardenFormat,
|
|
20
|
+
WardenLockMode,
|
|
21
|
+
} from './config.js';
|
|
22
|
+
import { resolveWardenConfig } from './config.js';
|
|
23
|
+
import { isDraftMarkedFile } from './draft.js';
|
|
13
24
|
import type { DriftResult } from './drift.js';
|
|
14
25
|
import { checkDrift } from './drift.js';
|
|
26
|
+
import {
|
|
27
|
+
collectProjectDocumentationImportResolutions,
|
|
28
|
+
collectProjectImportResolutions,
|
|
29
|
+
collectPublicWorkspaces,
|
|
30
|
+
} from './project-context.js';
|
|
15
31
|
import {
|
|
16
32
|
collectContourDefinitionIds,
|
|
17
33
|
collectContourReferenceTargetsByName,
|
|
@@ -27,27 +43,66 @@ import {
|
|
|
27
43
|
} from './rules/ast.js';
|
|
28
44
|
import { collectFileCrudCoverage } from './rules/incomplete-crud.js';
|
|
29
45
|
import { wardenRules, wardenTopoRules } from './rules/index.js';
|
|
46
|
+
import { getWardenRuleMetadata } from './rules/metadata.js';
|
|
30
47
|
import type {
|
|
31
48
|
ProjectAwareWardenRule,
|
|
32
49
|
ProjectContext,
|
|
33
50
|
TopoAwareWardenRule,
|
|
34
51
|
WardenDiagnostic,
|
|
52
|
+
WardenGuidanceLink,
|
|
35
53
|
WardenRule,
|
|
54
|
+
WardenRuleTier,
|
|
36
55
|
} from './rules/types.js';
|
|
56
|
+
import type { WardenImportResolution } from './resolve.js';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolved topo input for Warden runs that govern multiple apps.
|
|
60
|
+
*/
|
|
61
|
+
export interface WardenTopoTarget {
|
|
62
|
+
/** Stable app/topo label used to tag topo-aware diagnostics. */
|
|
63
|
+
readonly name?: string | undefined;
|
|
64
|
+
/** Resolved topo module to inspect. */
|
|
65
|
+
readonly topo: Topo;
|
|
66
|
+
}
|
|
37
67
|
|
|
38
68
|
/**
|
|
39
|
-
* Options for the
|
|
69
|
+
* Options for the shared Warden runner.
|
|
40
70
|
*/
|
|
41
|
-
export interface
|
|
71
|
+
export interface WardenRunOptions {
|
|
42
72
|
/** Root directory to scan for TypeScript files. Defaults to cwd. */
|
|
43
73
|
readonly rootDir?: string | undefined;
|
|
74
|
+
/** Warden config section from `trails.config.ts`, if already loaded. */
|
|
75
|
+
readonly config?: WardenConfigInput | undefined;
|
|
76
|
+
/** CLI/config-layer app names carried through shared resolution. */
|
|
77
|
+
readonly apps?: readonly string[] | undefined;
|
|
78
|
+
/** Cumulative analysis depth for the final M1 surfaces. */
|
|
79
|
+
readonly depth?: WardenDepth | undefined;
|
|
80
|
+
/** Draft-state handling mode for final M1 surfaces. */
|
|
81
|
+
readonly drafts?: EffectiveWardenConfig['drafts'] | undefined;
|
|
82
|
+
/** Failure threshold used to compute `report.passed`. */
|
|
83
|
+
readonly failOn?: WardenFailOn | undefined;
|
|
84
|
+
/** Output format requested by the caller. */
|
|
85
|
+
readonly format?: WardenFormat | undefined;
|
|
86
|
+
/** Lockfile mode requested by the caller. */
|
|
87
|
+
readonly lock?: WardenLockMode | undefined;
|
|
88
|
+
/** Suppress lockfile mutation for CI/pre-push callers. */
|
|
89
|
+
readonly noLockMutation?: boolean | undefined;
|
|
90
|
+
/** Environment layer for config resolution. Pass `process.env` at process boundaries. */
|
|
91
|
+
readonly env?: Record<string, string | undefined> | undefined;
|
|
44
92
|
/** Only run lint rules, skip drift detection */
|
|
45
93
|
readonly lintOnly?: boolean | undefined;
|
|
46
94
|
/** Only run drift detection, skip lint rules */
|
|
47
95
|
readonly driftOnly?: boolean | undefined;
|
|
48
96
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
97
|
+
* Run a single Warden tier. Defaults to all lint tiers plus drift.
|
|
98
|
+
*
|
|
99
|
+
* Selecting a non-drift tier skips drift detection; selecting `drift` skips
|
|
100
|
+
* lint rule dispatch. `lintOnly` and `driftOnly` remain compatibility shims.
|
|
101
|
+
*/
|
|
102
|
+
readonly tier?: WardenRuleTier | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* App topology for drift detection. When provided, enables real topology
|
|
105
|
+
* drift comparison and unlocks the topo-aware rule dispatch path.
|
|
51
106
|
*
|
|
52
107
|
* @remarks
|
|
53
108
|
* Topo-aware rules (both built-in `wardenTopoRules` and `extraTopoRules`)
|
|
@@ -56,6 +111,12 @@ export interface WardenOptions {
|
|
|
56
111
|
* must pass `topo` explicitly.
|
|
57
112
|
*/
|
|
58
113
|
readonly topo?: Topo | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Multiple resolved topos to govern in one invocation.
|
|
116
|
+
*
|
|
117
|
+
* Source/project rules run once; topo-aware rules run once per target.
|
|
118
|
+
*/
|
|
119
|
+
readonly topos?: readonly WardenTopoTarget[] | undefined;
|
|
59
120
|
/**
|
|
60
121
|
* Extra topo-aware rules to run in addition to the built-in registry.
|
|
61
122
|
*
|
|
@@ -66,6 +127,9 @@ export interface WardenOptions {
|
|
|
66
127
|
readonly extraTopoRules?: readonly TopoAwareWardenRule[] | undefined;
|
|
67
128
|
}
|
|
68
129
|
|
|
130
|
+
/** Backwards-compatible name for older consumers. */
|
|
131
|
+
export type WardenOptions = WardenRunOptions;
|
|
132
|
+
|
|
69
133
|
/**
|
|
70
134
|
* Result of a warden run.
|
|
71
135
|
*/
|
|
@@ -80,33 +144,138 @@ export interface WardenReport {
|
|
|
80
144
|
readonly drift: DriftResult | null;
|
|
81
145
|
/** Whether the warden run passed (no errors, no drift) */
|
|
82
146
|
readonly passed: boolean;
|
|
147
|
+
/** Effective shared config consumed by this run. */
|
|
148
|
+
readonly effectiveConfig?: EffectiveWardenConfig | undefined;
|
|
149
|
+
/** Resolved topo/app labels governed by this run. */
|
|
150
|
+
readonly topoNames?: readonly string[] | undefined;
|
|
83
151
|
}
|
|
84
152
|
|
|
85
153
|
/**
|
|
86
|
-
* Collect
|
|
154
|
+
* Collect Warden scan targets under a directory, excluding generated and test
|
|
155
|
+
* surfaces that should not contribute most committed-source diagnostics.
|
|
87
156
|
*/
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
157
|
+
const isInfrastructureScanTarget = (match: string): boolean =>
|
|
158
|
+
match.endsWith('.d.ts') ||
|
|
159
|
+
match.startsWith('node_modules/') ||
|
|
160
|
+
match.startsWith('dist/') ||
|
|
161
|
+
match.startsWith('.git/');
|
|
162
|
+
|
|
163
|
+
const isTestScanTarget = (match: string): boolean =>
|
|
164
|
+
match.includes('__tests__/') ||
|
|
165
|
+
match.includes('__test__/') ||
|
|
166
|
+
match.endsWith('.test.ts') ||
|
|
167
|
+
match.endsWith('.spec.ts');
|
|
168
|
+
|
|
169
|
+
const isAllowedScanTarget = (match: string): boolean =>
|
|
170
|
+
!isInfrastructureScanTarget(match) && !isTestScanTarget(match);
|
|
171
|
+
|
|
172
|
+
const isDevPermitTestScanTarget = (match: string): boolean =>
|
|
173
|
+
!isInfrastructureScanTarget(match) && isTestScanTarget(match);
|
|
174
|
+
|
|
175
|
+
const collectFilesMatching = (
|
|
176
|
+
dir: string,
|
|
177
|
+
pattern: string,
|
|
178
|
+
dot = false
|
|
179
|
+
): readonly string[] => {
|
|
180
|
+
const glob = new Bun.Glob(pattern);
|
|
181
|
+
let matches: IterableIterator<string>;
|
|
182
|
+
try {
|
|
183
|
+
matches = glob.scanSync({ cwd: dir, dot, onlyFiles: true });
|
|
184
|
+
} catch {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const files: string[] = [];
|
|
189
|
+
for (const match of matches) {
|
|
190
|
+
if (isAllowedScanTarget(match)) {
|
|
191
|
+
files.push(`${dir}/${match}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return files;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const collectTsFiles = (dir: string): readonly string[] =>
|
|
198
|
+
collectFilesMatching(dir, '**/*.ts');
|
|
199
|
+
|
|
200
|
+
const draftModeIncludesFile = (
|
|
201
|
+
filePath: string,
|
|
202
|
+
drafts: EffectiveWardenConfig['drafts']
|
|
203
|
+
): boolean => {
|
|
204
|
+
const isDraftFile = isDraftMarkedFile(filePath);
|
|
205
|
+
if (drafts === 'exclude') {
|
|
206
|
+
return !isDraftFile;
|
|
207
|
+
}
|
|
208
|
+
if (drafts === 'only') {
|
|
209
|
+
return isDraftFile;
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const filterSourceFilesByDraftMode = (
|
|
215
|
+
sourceFiles: readonly SourceFile[],
|
|
216
|
+
drafts: EffectiveWardenConfig['drafts']
|
|
217
|
+
): readonly SourceFile[] =>
|
|
218
|
+
drafts === 'include'
|
|
219
|
+
? sourceFiles
|
|
220
|
+
: sourceFiles.filter((sourceFile) =>
|
|
221
|
+
draftModeIncludesFile(sourceFile.filePath, drafts)
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const collectDevPermitTestFiles = (dir: string): readonly string[] => {
|
|
99
225
|
const glob = new Bun.Glob('**/*.ts');
|
|
100
226
|
let matches: IterableIterator<string>;
|
|
101
227
|
try {
|
|
102
|
-
matches = glob.scanSync({ cwd: dir,
|
|
228
|
+
matches = glob.scanSync({ cwd: dir, onlyFiles: true });
|
|
229
|
+
} catch {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const files: string[] = [];
|
|
234
|
+
for (const match of matches) {
|
|
235
|
+
if (isDevPermitTestScanTarget(match)) {
|
|
236
|
+
files.push(`${dir}/${match}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return files;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const collectTextScanFiles = (dir: string): readonly string[] => [
|
|
243
|
+
...collectFilesMatching(dir, '**/*.sh', true),
|
|
244
|
+
...collectFilesMatching(dir, '**/*.bash', true),
|
|
245
|
+
...collectFilesMatching(dir, '**/*.zsh', true),
|
|
246
|
+
...collectFilesMatching(dir, '**/*.yml', true),
|
|
247
|
+
...collectFilesMatching(dir, '**/*.yaml', true),
|
|
248
|
+
...collectFilesMatching(dir, '**/package.json', true),
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const isDocumentationScanTarget = (match: string): boolean => {
|
|
252
|
+
if (match === 'README.md') {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (/^(?:packages|adapters|apps)\/[^/]+\/README\.md$/.test(match)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return (
|
|
259
|
+
match.startsWith('docs/') &&
|
|
260
|
+
!match.startsWith('docs/adr/') &&
|
|
261
|
+
!match.startsWith('docs/migration/') &&
|
|
262
|
+
!match.startsWith('docs/releases/') &&
|
|
263
|
+
match.endsWith('.md')
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const collectDocumentationFiles = (dir: string): readonly string[] => {
|
|
268
|
+
const glob = new Bun.Glob('**/*.md');
|
|
269
|
+
let matches: IterableIterator<string>;
|
|
270
|
+
try {
|
|
271
|
+
matches = glob.scanSync({ cwd: dir, onlyFiles: true });
|
|
103
272
|
} catch {
|
|
104
273
|
return [];
|
|
105
274
|
}
|
|
106
275
|
|
|
107
276
|
const files: string[] = [];
|
|
108
277
|
for (const match of matches) {
|
|
109
|
-
if (
|
|
278
|
+
if (isAllowedScanTarget(match) && isDocumentationScanTarget(match)) {
|
|
110
279
|
files.push(`${dir}/${match}`);
|
|
111
280
|
}
|
|
112
281
|
}
|
|
@@ -115,6 +284,7 @@ const collectTsFiles = (dir: string): readonly string[] => {
|
|
|
115
284
|
|
|
116
285
|
interface SourceFile {
|
|
117
286
|
readonly filePath: string;
|
|
287
|
+
readonly kind: 'documentation' | 'text' | 'typescript';
|
|
118
288
|
readonly sourceCode: string;
|
|
119
289
|
}
|
|
120
290
|
|
|
@@ -127,7 +297,13 @@ interface MutableProjectContext {
|
|
|
127
297
|
knownResourceIds: Set<string>;
|
|
128
298
|
knownSignalIds: Set<string>;
|
|
129
299
|
knownTrailIds: Set<string>;
|
|
300
|
+
importResolutionsByFile: Map<string, readonly WardenImportResolution[]>;
|
|
301
|
+
documentedImportResolutionsByFile: Map<
|
|
302
|
+
string,
|
|
303
|
+
readonly WardenImportResolution[]
|
|
304
|
+
>;
|
|
130
305
|
onTargetSignalIds: Set<string>;
|
|
306
|
+
publicWorkspaces: ReturnType<typeof collectPublicWorkspaces>;
|
|
131
307
|
reconcileTableIds: Set<string>;
|
|
132
308
|
trailIntentsById: Map<string, 'destroy' | 'read' | 'write'>;
|
|
133
309
|
}
|
|
@@ -137,11 +313,17 @@ const createMutableProjectContext = (): MutableProjectContext => ({
|
|
|
137
313
|
crossTargetTrailIds: new Set<string>(),
|
|
138
314
|
crudCoverageByEntity: new Map<string, Set<string>>(),
|
|
139
315
|
crudTableIds: new Set<string>(),
|
|
316
|
+
documentedImportResolutionsByFile: new Map<
|
|
317
|
+
string,
|
|
318
|
+
readonly WardenImportResolution[]
|
|
319
|
+
>(),
|
|
320
|
+
importResolutionsByFile: new Map<string, readonly WardenImportResolution[]>(),
|
|
140
321
|
knownContourIds: new Set<string>(),
|
|
141
322
|
knownResourceIds: new Set<string>(),
|
|
142
323
|
knownSignalIds: new Set<string>(),
|
|
143
324
|
knownTrailIds: new Set<string>(),
|
|
144
325
|
onTargetSignalIds: new Set<string>(),
|
|
326
|
+
publicWorkspaces: new Map(),
|
|
145
327
|
reconcileTableIds: new Set<string>(),
|
|
146
328
|
trailIntentsById: new Map<string, 'destroy' | 'read' | 'write'>(),
|
|
147
329
|
});
|
|
@@ -192,9 +374,21 @@ const toProjectContext = (context: MutableProjectContext): ProjectContext => ({
|
|
|
192
374
|
knownResourceIds: context.knownResourceIds,
|
|
193
375
|
knownSignalIds: context.knownSignalIds,
|
|
194
376
|
knownTrailIds: context.knownTrailIds,
|
|
377
|
+
...(context.importResolutionsByFile.size > 0
|
|
378
|
+
? { importResolutionsByFile: context.importResolutionsByFile }
|
|
379
|
+
: {}),
|
|
380
|
+
...(context.documentedImportResolutionsByFile.size > 0
|
|
381
|
+
? {
|
|
382
|
+
documentedImportResolutionsByFile:
|
|
383
|
+
context.documentedImportResolutionsByFile,
|
|
384
|
+
}
|
|
385
|
+
: {}),
|
|
195
386
|
...(context.onTargetSignalIds.size > 0
|
|
196
387
|
? { onTargetSignalIds: context.onTargetSignalIds }
|
|
197
388
|
: {}),
|
|
389
|
+
...(context.publicWorkspaces.size > 0
|
|
390
|
+
? { publicWorkspaces: context.publicWorkspaces }
|
|
391
|
+
: {}),
|
|
198
392
|
...(context.reconcileTableIds.size > 0
|
|
199
393
|
? { reconcileTableIds: context.reconcileTableIds }
|
|
200
394
|
: {}),
|
|
@@ -357,6 +551,43 @@ const loadSourceFiles = async (
|
|
|
357
551
|
try {
|
|
358
552
|
sourceFiles.push({
|
|
359
553
|
filePath,
|
|
554
|
+
kind: 'typescript',
|
|
555
|
+
sourceCode: await Bun.file(filePath).text(),
|
|
556
|
+
});
|
|
557
|
+
} catch {
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
for (const filePath of collectTextScanFiles(rootDir)) {
|
|
563
|
+
try {
|
|
564
|
+
sourceFiles.push({
|
|
565
|
+
filePath,
|
|
566
|
+
kind: 'text',
|
|
567
|
+
sourceCode: await Bun.file(filePath).text(),
|
|
568
|
+
});
|
|
569
|
+
} catch {
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
for (const filePath of collectDocumentationFiles(rootDir)) {
|
|
575
|
+
try {
|
|
576
|
+
sourceFiles.push({
|
|
577
|
+
filePath,
|
|
578
|
+
kind: 'documentation',
|
|
579
|
+
sourceCode: await Bun.file(filePath).text(),
|
|
580
|
+
});
|
|
581
|
+
} catch {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
for (const filePath of collectDevPermitTestFiles(rootDir)) {
|
|
587
|
+
try {
|
|
588
|
+
sourceFiles.push({
|
|
589
|
+
filePath,
|
|
590
|
+
kind: 'text',
|
|
360
591
|
sourceCode: await Bun.file(filePath).text(),
|
|
361
592
|
});
|
|
362
593
|
} catch {
|
|
@@ -517,26 +748,70 @@ const collectFileContourReferences = (
|
|
|
517
748
|
}
|
|
518
749
|
};
|
|
519
750
|
|
|
751
|
+
const collectFileImportResolutions = (
|
|
752
|
+
rootDir: string,
|
|
753
|
+
sourceFiles: readonly SourceFile[],
|
|
754
|
+
context: MutableProjectContext
|
|
755
|
+
): void => {
|
|
756
|
+
const resolutionsByFile = collectProjectImportResolutions({
|
|
757
|
+
rootDir,
|
|
758
|
+
sourceFiles,
|
|
759
|
+
});
|
|
760
|
+
for (const [filePath, resolutions] of resolutionsByFile) {
|
|
761
|
+
context.importResolutionsByFile.set(filePath, resolutions);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const collectFileDocumentedImportResolutions = (
|
|
766
|
+
rootDir: string,
|
|
767
|
+
sourceFiles: readonly SourceFile[],
|
|
768
|
+
context: MutableProjectContext
|
|
769
|
+
): void => {
|
|
770
|
+
const resolutionsByFile = collectProjectDocumentationImportResolutions({
|
|
771
|
+
rootDir,
|
|
772
|
+
sourceFiles,
|
|
773
|
+
});
|
|
774
|
+
for (const [filePath, resolutions] of resolutionsByFile) {
|
|
775
|
+
context.documentedImportResolutionsByFile.set(filePath, resolutions);
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
520
779
|
const buildProjectContext = (
|
|
521
780
|
sourceFiles: readonly SourceFile[],
|
|
522
|
-
|
|
781
|
+
rootDir: string,
|
|
782
|
+
appTopos: readonly Topo[] = []
|
|
523
783
|
): ProjectContext => {
|
|
524
784
|
const context = createMutableProjectContext();
|
|
785
|
+
const typeScriptSourceFiles = sourceFiles.filter(
|
|
786
|
+
(sourceFile) => sourceFile.kind === 'typescript'
|
|
787
|
+
);
|
|
788
|
+
const documentationSourceFiles = sourceFiles.filter(
|
|
789
|
+
(sourceFile) => sourceFile.kind === 'documentation'
|
|
790
|
+
);
|
|
791
|
+
context.publicWorkspaces = collectPublicWorkspaces(rootDir);
|
|
525
792
|
|
|
526
|
-
if (
|
|
527
|
-
|
|
528
|
-
|
|
793
|
+
if (appTopos.length > 0) {
|
|
794
|
+
for (const appTopo of appTopos) {
|
|
795
|
+
collectTopoTrailContext(appTopo, context);
|
|
796
|
+
}
|
|
797
|
+
for (const sourceFile of typeScriptSourceFiles) {
|
|
529
798
|
collectFileSupplementalProjectContext(sourceFile, context);
|
|
530
799
|
}
|
|
531
800
|
} else {
|
|
532
|
-
for (const sourceFile of
|
|
801
|
+
for (const sourceFile of typeScriptSourceFiles) {
|
|
533
802
|
collectFileProjectContext(sourceFile, context);
|
|
534
803
|
}
|
|
535
804
|
}
|
|
536
805
|
|
|
537
|
-
for (const sourceFile of
|
|
806
|
+
for (const sourceFile of typeScriptSourceFiles) {
|
|
538
807
|
collectFileContourReferences(sourceFile, context);
|
|
539
808
|
}
|
|
809
|
+
collectFileImportResolutions(rootDir, typeScriptSourceFiles, context);
|
|
810
|
+
collectFileDocumentedImportResolutions(
|
|
811
|
+
rootDir,
|
|
812
|
+
documentationSourceFiles,
|
|
813
|
+
context
|
|
814
|
+
);
|
|
540
815
|
|
|
541
816
|
return toProjectContext(context);
|
|
542
817
|
};
|
|
@@ -544,6 +819,117 @@ const buildProjectContext = (
|
|
|
544
819
|
const isProjectAwareRule = (rule: WardenRule): rule is ProjectAwareWardenRule =>
|
|
545
820
|
'checkWithContext' in rule;
|
|
546
821
|
|
|
822
|
+
const createOptionsDiagnostic = (message: string): WardenDiagnostic => ({
|
|
823
|
+
filePath: '<warden-options>',
|
|
824
|
+
line: 1,
|
|
825
|
+
message,
|
|
826
|
+
rule: 'warden-options',
|
|
827
|
+
severity: 'error',
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
interface WardenRuleSelector {
|
|
831
|
+
readonly depth?: WardenDepth | undefined;
|
|
832
|
+
readonly tier?: WardenRuleTier | undefined;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const depthIncludesTier = (
|
|
836
|
+
depth: WardenDepth,
|
|
837
|
+
tier: WardenRuleTier
|
|
838
|
+
): boolean => {
|
|
839
|
+
switch (depth) {
|
|
840
|
+
case 'source': {
|
|
841
|
+
return tier === 'source-static';
|
|
842
|
+
}
|
|
843
|
+
case 'project': {
|
|
844
|
+
return tier === 'source-static' || tier === 'project-static';
|
|
845
|
+
}
|
|
846
|
+
case 'topo': {
|
|
847
|
+
return (
|
|
848
|
+
tier === 'source-static' ||
|
|
849
|
+
tier === 'project-static' ||
|
|
850
|
+
tier === 'topo-aware'
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
case 'all': {
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
default: {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const ruleMatchesTier = (
|
|
863
|
+
metadata: ReturnType<typeof getWardenRuleMetadata>,
|
|
864
|
+
tier: WardenRuleTier | undefined
|
|
865
|
+
): boolean => {
|
|
866
|
+
if (!tier) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (!metadata) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return tier === 'advisory'
|
|
875
|
+
? metadata.scope === 'advisory'
|
|
876
|
+
: metadata.tier === tier;
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const ruleMatchesDepth = (
|
|
880
|
+
metadata: ReturnType<typeof getWardenRuleMetadata>,
|
|
881
|
+
depth: WardenDepth | undefined
|
|
882
|
+
): boolean => {
|
|
883
|
+
if (!depth) {
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (!metadata) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (metadata.scope === 'advisory') {
|
|
892
|
+
return depth === 'all';
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
return depthIncludesTier(depth, metadata.tier);
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const isSelectedRule = (
|
|
899
|
+
rule: WardenRule | TopoAwareWardenRule,
|
|
900
|
+
selector: WardenRuleSelector
|
|
901
|
+
): boolean => {
|
|
902
|
+
const metadata = getWardenRuleMetadata(rule);
|
|
903
|
+
return selector.tier
|
|
904
|
+
? ruleMatchesTier(metadata, selector.tier)
|
|
905
|
+
: ruleMatchesDepth(metadata, selector.depth);
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
const isSelectedTopoRule = (
|
|
909
|
+
rule: TopoAwareWardenRule,
|
|
910
|
+
selector: WardenRuleSelector
|
|
911
|
+
): boolean => {
|
|
912
|
+
const metadata = getWardenRuleMetadata(rule);
|
|
913
|
+
if (selector.tier) {
|
|
914
|
+
return metadata
|
|
915
|
+
? ruleMatchesTier(metadata, selector.tier)
|
|
916
|
+
: selector.tier === 'topo-aware';
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return metadata ? ruleMatchesDepth(metadata, selector.depth) : true;
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const withDiagnosticGuidance = (
|
|
923
|
+
diagnostic: WardenDiagnostic
|
|
924
|
+
): WardenDiagnostic => {
|
|
925
|
+
if (diagnostic.guidance !== undefined) {
|
|
926
|
+
return diagnostic;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const guidance = getWardenRuleMetadata(diagnostic.rule)?.guidance;
|
|
930
|
+
return guidance === undefined ? diagnostic : { ...diagnostic, guidance };
|
|
931
|
+
};
|
|
932
|
+
|
|
547
933
|
const topoRuleFailureDiagnostic = (
|
|
548
934
|
rule: TopoAwareWardenRule,
|
|
549
935
|
error: unknown
|
|
@@ -566,13 +952,14 @@ const topoRuleFailureDiagnostic = (
|
|
|
566
952
|
*/
|
|
567
953
|
const lintTopo = async (
|
|
568
954
|
appTopo: Topo,
|
|
569
|
-
extraTopoRules: readonly TopoAwareWardenRule[]
|
|
955
|
+
extraTopoRules: readonly TopoAwareWardenRule[],
|
|
956
|
+
selector: WardenRuleSelector
|
|
570
957
|
): Promise<readonly WardenDiagnostic[]> => {
|
|
571
958
|
const diagnostics: WardenDiagnostic[] = [];
|
|
572
959
|
const rules: readonly TopoAwareWardenRule[] = [
|
|
573
960
|
...wardenTopoRules.values(),
|
|
574
961
|
...extraTopoRules,
|
|
575
|
-
];
|
|
962
|
+
].filter((rule) => isSelectedTopoRule(rule, selector));
|
|
576
963
|
for (const rule of rules) {
|
|
577
964
|
try {
|
|
578
965
|
diagnostics.push(...(await rule.checkTopo(appTopo)));
|
|
@@ -585,11 +972,31 @@ const lintTopo = async (
|
|
|
585
972
|
|
|
586
973
|
const lintSourceFiles = (
|
|
587
974
|
sourceFiles: readonly SourceFile[],
|
|
588
|
-
context: ProjectContext
|
|
975
|
+
context: ProjectContext,
|
|
976
|
+
selector: WardenRuleSelector
|
|
589
977
|
): readonly WardenDiagnostic[] => {
|
|
590
978
|
const diagnostics: WardenDiagnostic[] = [];
|
|
591
979
|
for (const sourceFile of sourceFiles) {
|
|
592
980
|
for (const rule of wardenRules.values()) {
|
|
981
|
+
if (
|
|
982
|
+
sourceFile.kind === 'text' &&
|
|
983
|
+
rule.name !== 'no-dev-permit-in-source' &&
|
|
984
|
+
rule.name !== 'public-internal-deep-imports'
|
|
985
|
+
) {
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (
|
|
990
|
+
sourceFile.kind === 'documentation' &&
|
|
991
|
+
rule.name !== 'public-internal-deep-imports'
|
|
992
|
+
) {
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (!isSelectedRule(rule, selector)) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
|
|
593
1000
|
if (isProjectAwareRule(rule)) {
|
|
594
1001
|
diagnostics.push(
|
|
595
1002
|
...rule.checkWithContext(
|
|
@@ -608,58 +1015,323 @@ const lintSourceFiles = (
|
|
|
608
1015
|
return diagnostics;
|
|
609
1016
|
};
|
|
610
1017
|
|
|
1018
|
+
const tagTopoDiagnostic = (
|
|
1019
|
+
diagnostic: WardenDiagnostic,
|
|
1020
|
+
topoName: string | undefined
|
|
1021
|
+
): WardenDiagnostic =>
|
|
1022
|
+
topoName === undefined ? diagnostic : { ...diagnostic, topoName };
|
|
1023
|
+
|
|
1024
|
+
const lintTopoTargets = async (
|
|
1025
|
+
topoTargets: readonly WardenTopoTarget[],
|
|
1026
|
+
extraTopoRules: readonly TopoAwareWardenRule[],
|
|
1027
|
+
selector: WardenRuleSelector,
|
|
1028
|
+
tagDiagnostics: boolean
|
|
1029
|
+
): Promise<readonly WardenDiagnostic[]> => {
|
|
1030
|
+
const diagnostics: WardenDiagnostic[] = [];
|
|
1031
|
+
|
|
1032
|
+
for (const target of topoTargets) {
|
|
1033
|
+
const topoDiagnostics = await lintTopo(
|
|
1034
|
+
target.topo,
|
|
1035
|
+
extraTopoRules,
|
|
1036
|
+
selector
|
|
1037
|
+
);
|
|
1038
|
+
const topoName = target.name ?? target.topo.name;
|
|
1039
|
+
diagnostics.push(
|
|
1040
|
+
...(tagDiagnostics
|
|
1041
|
+
? topoDiagnostics.map((diagnostic) =>
|
|
1042
|
+
tagTopoDiagnostic(diagnostic, topoName)
|
|
1043
|
+
)
|
|
1044
|
+
: topoDiagnostics)
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return diagnostics;
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
const selectorIncludesTopoRules = (selector: WardenRuleSelector): boolean => {
|
|
1052
|
+
if (selector.tier) {
|
|
1053
|
+
return selector.tier === 'advisory';
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
return !selector.depth || depthIncludesTier(selector.depth, 'topo-aware');
|
|
1057
|
+
};
|
|
1058
|
+
|
|
611
1059
|
/**
|
|
612
1060
|
* Lint all files against all warden rules.
|
|
613
1061
|
*/
|
|
614
1062
|
const lintFiles = async (
|
|
615
1063
|
rootDir: string,
|
|
616
|
-
|
|
617
|
-
|
|
1064
|
+
drafts: EffectiveWardenConfig['drafts'],
|
|
1065
|
+
topoTargets: readonly WardenTopoTarget[],
|
|
1066
|
+
extraTopoRules: readonly TopoAwareWardenRule[],
|
|
1067
|
+
selector: WardenRuleSelector
|
|
618
1068
|
): Promise<WardenDiagnostic[]> => {
|
|
619
|
-
|
|
620
|
-
|
|
1069
|
+
if (selector.tier === 'topo-aware') {
|
|
1070
|
+
return [
|
|
1071
|
+
...(await lintTopoTargets(topoTargets, extraTopoRules, selector, true)),
|
|
1072
|
+
];
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const sourceFiles = filterSourceFilesByDraftMode(
|
|
1076
|
+
await loadSourceFiles(rootDir),
|
|
1077
|
+
drafts
|
|
1078
|
+
);
|
|
1079
|
+
const context = buildProjectContext(
|
|
1080
|
+
sourceFiles,
|
|
1081
|
+
rootDir,
|
|
1082
|
+
topoTargets.map((target) => target.topo)
|
|
1083
|
+
);
|
|
621
1084
|
const allDiagnostics: WardenDiagnostic[] = [
|
|
622
|
-
...lintSourceFiles(sourceFiles, context),
|
|
1085
|
+
...lintSourceFiles(sourceFiles, context, selector),
|
|
623
1086
|
];
|
|
624
1087
|
|
|
625
|
-
if (
|
|
626
|
-
|
|
1088
|
+
if (
|
|
1089
|
+
topoTargets.length > 0 &&
|
|
1090
|
+
(selector.tier === undefined || selector.tier === 'advisory') &&
|
|
1091
|
+
selectorIncludesTopoRules(selector)
|
|
1092
|
+
) {
|
|
1093
|
+
allDiagnostics.push(
|
|
1094
|
+
...(await lintTopoTargets(
|
|
1095
|
+
topoTargets,
|
|
1096
|
+
extraTopoRules,
|
|
1097
|
+
selector,
|
|
1098
|
+
topoTargets.length > 1
|
|
1099
|
+
))
|
|
1100
|
+
);
|
|
627
1101
|
}
|
|
628
1102
|
|
|
629
1103
|
return allDiagnostics;
|
|
630
1104
|
};
|
|
631
1105
|
|
|
1106
|
+
const topoTargetsFromOptions = (
|
|
1107
|
+
options: WardenRunOptions
|
|
1108
|
+
): readonly WardenTopoTarget[] => {
|
|
1109
|
+
if (options.topos !== undefined && options.topos.length > 0) {
|
|
1110
|
+
return options.topos;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
return options.topo ? [{ name: options.topo.name, topo: options.topo }] : [];
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
const aggregateDriftHash = (
|
|
1117
|
+
topoTargets: readonly WardenTopoTarget[],
|
|
1118
|
+
driftResults: readonly DriftResult[]
|
|
1119
|
+
): string => {
|
|
1120
|
+
const currentHashes = new Set(
|
|
1121
|
+
driftResults.map((result) => result.currentHash)
|
|
1122
|
+
);
|
|
1123
|
+
const [onlyHash] = currentHashes;
|
|
1124
|
+
if (currentHashes.size === 1 && onlyHash !== undefined) {
|
|
1125
|
+
return onlyHash;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const payload = driftResults
|
|
1129
|
+
.map((result, index) => {
|
|
1130
|
+
const target = topoTargets[index];
|
|
1131
|
+
return {
|
|
1132
|
+
currentHash: result.currentHash,
|
|
1133
|
+
topoName: target?.name ?? target?.topo.name ?? `topo-${String(index)}`,
|
|
1134
|
+
};
|
|
1135
|
+
})
|
|
1136
|
+
.toSorted((left, right) => left.topoName.localeCompare(right.topoName));
|
|
1137
|
+
const hasher = new Bun.CryptoHasher('sha256');
|
|
1138
|
+
hasher.update(JSON.stringify(payload));
|
|
1139
|
+
return hasher.digest('hex');
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
const describeTopoDriftHash = (
|
|
1143
|
+
topoTargets: readonly WardenTopoTarget[],
|
|
1144
|
+
driftResults: readonly DriftResult[]
|
|
1145
|
+
): string =>
|
|
1146
|
+
driftResults
|
|
1147
|
+
.map((result, index) => {
|
|
1148
|
+
const target = topoTargets[index];
|
|
1149
|
+
const topoName =
|
|
1150
|
+
target?.name ?? target?.topo.name ?? `topo-${String(index)}`;
|
|
1151
|
+
return `${topoName}=${result.committedHash ?? '<none>'}`;
|
|
1152
|
+
})
|
|
1153
|
+
.join(', ');
|
|
1154
|
+
|
|
1155
|
+
const checkDriftForTopoTargets = async (
|
|
1156
|
+
rootDir: string,
|
|
1157
|
+
topoTargets: readonly WardenTopoTarget[]
|
|
1158
|
+
): Promise<DriftResult> => {
|
|
1159
|
+
if (topoTargets.length <= 1) {
|
|
1160
|
+
return checkDrift(rootDir, topoTargets[0]?.topo);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const driftResults = await Promise.all(
|
|
1164
|
+
topoTargets.map((target) => checkDrift(rootDir, target.topo))
|
|
1165
|
+
);
|
|
1166
|
+
const committedHashes = new Set(
|
|
1167
|
+
driftResults.map((result) => result.committedHash)
|
|
1168
|
+
);
|
|
1169
|
+
if (committedHashes.size > 1) {
|
|
1170
|
+
return {
|
|
1171
|
+
blockedReason: `multi-topo drift expected one committed trails.lock hash but found conflicting hashes: ${describeTopoDriftHash(topoTargets, driftResults)}`,
|
|
1172
|
+
committedHash: null,
|
|
1173
|
+
currentHash: 'blocked',
|
|
1174
|
+
stale: true,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
const committedHash = driftResults[0]?.committedHash ?? null;
|
|
1178
|
+
const blockedReasons = driftResults.flatMap((result, index) => {
|
|
1179
|
+
if (result.blockedReason === undefined) {
|
|
1180
|
+
return [];
|
|
1181
|
+
}
|
|
1182
|
+
const target = topoTargets[index];
|
|
1183
|
+
const topoName =
|
|
1184
|
+
target?.name ?? target?.topo.name ?? `topo-${String(index)}`;
|
|
1185
|
+
return [`${topoName}: ${result.blockedReason}`];
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
if (blockedReasons.length > 0) {
|
|
1189
|
+
return {
|
|
1190
|
+
blockedReason: blockedReasons.join('; '),
|
|
1191
|
+
committedHash,
|
|
1192
|
+
currentHash: 'blocked',
|
|
1193
|
+
stale: true,
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const currentHash = aggregateDriftHash(topoTargets, driftResults);
|
|
1198
|
+
return {
|
|
1199
|
+
committedHash,
|
|
1200
|
+
currentHash,
|
|
1201
|
+
stale: committedHash !== null && committedHash !== currentHash,
|
|
1202
|
+
};
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
const shouldRunLint = (options: WardenRunOptions): boolean =>
|
|
1206
|
+
options.tier ? options.tier !== 'drift' : !options.driftOnly;
|
|
1207
|
+
|
|
1208
|
+
const shouldRunDrift = (
|
|
1209
|
+
options: WardenRunOptions,
|
|
1210
|
+
effectiveConfig: EffectiveWardenConfig
|
|
1211
|
+
): boolean => {
|
|
1212
|
+
if (effectiveConfig.lock === 'skip') {
|
|
1213
|
+
return false;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (options.tier) {
|
|
1217
|
+
return options.tier === 'drift';
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (options.lintOnly) {
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return options.driftOnly || effectiveConfig.depth === 'all';
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
const reportPassed = ({
|
|
1228
|
+
drift,
|
|
1229
|
+
errorCount,
|
|
1230
|
+
failOn,
|
|
1231
|
+
warnCount,
|
|
1232
|
+
}: {
|
|
1233
|
+
readonly drift: DriftResult | null;
|
|
1234
|
+
readonly errorCount: number;
|
|
1235
|
+
readonly failOn: WardenFailOn;
|
|
1236
|
+
readonly warnCount: number;
|
|
1237
|
+
}): boolean =>
|
|
1238
|
+
errorCount === 0 &&
|
|
1239
|
+
(failOn === 'error' || warnCount === 0) &&
|
|
1240
|
+
!(drift?.stale ?? false) &&
|
|
1241
|
+
drift?.blockedReason === undefined;
|
|
1242
|
+
|
|
1243
|
+
const buildCliConfigLayer = (options: WardenRunOptions): WardenConfigLayer => ({
|
|
1244
|
+
...(options.apps ? { apps: [...options.apps] } : {}),
|
|
1245
|
+
...(options.depth ? { depth: options.depth } : {}),
|
|
1246
|
+
...(options.drafts ? { drafts: options.drafts } : {}),
|
|
1247
|
+
...(options.failOn ? { failOn: options.failOn } : {}),
|
|
1248
|
+
...(options.format ? { format: options.format } : {}),
|
|
1249
|
+
...(options.lock ? { lock: options.lock } : {}),
|
|
1250
|
+
...(options.noLockMutation === undefined
|
|
1251
|
+
? {}
|
|
1252
|
+
: { noLockMutation: options.noLockMutation }),
|
|
1253
|
+
});
|
|
1254
|
+
|
|
632
1255
|
/**
|
|
633
1256
|
* Run all warden checks and return a structured report.
|
|
634
1257
|
*/
|
|
635
1258
|
export const runWarden = async (
|
|
636
|
-
options:
|
|
1259
|
+
options: WardenRunOptions = {}
|
|
637
1260
|
): Promise<WardenReport> => {
|
|
638
1261
|
const rootDir = resolve(options.rootDir ?? process.cwd());
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1262
|
+
const { diagnostics: configDiagnostics, effectiveConfig } =
|
|
1263
|
+
resolveWardenConfig({
|
|
1264
|
+
cli: buildCliConfigLayer(options),
|
|
1265
|
+
config: options.config,
|
|
1266
|
+
env: options.env,
|
|
1267
|
+
});
|
|
1268
|
+
const optionDiagnostics =
|
|
1269
|
+
!options.tier && options.lintOnly && options.driftOnly
|
|
1270
|
+
? [
|
|
1271
|
+
createOptionsDiagnostic(
|
|
1272
|
+
'lintOnly and driftOnly cannot both be true. Use tier to select a single Warden mode.'
|
|
1273
|
+
),
|
|
1274
|
+
]
|
|
1275
|
+
: [];
|
|
1276
|
+
const topoTargets = topoTargetsFromOptions(options);
|
|
1277
|
+
const selector = {
|
|
1278
|
+
depth: options.tier ? undefined : effectiveConfig.depth,
|
|
1279
|
+
tier: options.tier,
|
|
1280
|
+
} satisfies WardenRuleSelector;
|
|
1281
|
+
const runLint = shouldRunLint(options);
|
|
1282
|
+
const runDrift = shouldRunDrift(options, effectiveConfig);
|
|
1283
|
+
|
|
1284
|
+
const rawDiagnostics = [
|
|
1285
|
+
...configDiagnostics,
|
|
1286
|
+
...optionDiagnostics,
|
|
1287
|
+
...(runLint
|
|
1288
|
+
? await lintFiles(
|
|
1289
|
+
rootDir,
|
|
1290
|
+
effectiveConfig.drafts,
|
|
1291
|
+
topoTargets,
|
|
1292
|
+
options.extraTopoRules ?? [],
|
|
1293
|
+
selector
|
|
1294
|
+
)
|
|
1295
|
+
: []),
|
|
1296
|
+
];
|
|
1297
|
+
const allDiagnostics = rawDiagnostics.map(withDiagnosticGuidance);
|
|
1298
|
+
const drift = runDrift
|
|
1299
|
+
? await checkDriftForTopoTargets(rootDir, topoTargets)
|
|
1300
|
+
: null;
|
|
645
1301
|
|
|
646
1302
|
const errorCount = allDiagnostics.filter(
|
|
647
1303
|
(d) => d.severity === 'error'
|
|
648
1304
|
).length;
|
|
649
1305
|
const warnCount = allDiagnostics.filter((d) => d.severity === 'warn').length;
|
|
1306
|
+
const topoNames =
|
|
1307
|
+
topoTargets.length > 0
|
|
1308
|
+
? topoTargets.map((target) => target.name ?? target.topo.name)
|
|
1309
|
+
: undefined;
|
|
650
1310
|
|
|
651
1311
|
return {
|
|
652
1312
|
diagnostics: allDiagnostics,
|
|
653
1313
|
drift,
|
|
1314
|
+
effectiveConfig,
|
|
654
1315
|
errorCount,
|
|
655
|
-
passed:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1316
|
+
passed: reportPassed({
|
|
1317
|
+
drift,
|
|
1318
|
+
errorCount,
|
|
1319
|
+
failOn: effectiveConfig.failOn,
|
|
1320
|
+
warnCount,
|
|
1321
|
+
}),
|
|
1322
|
+
...(topoNames === undefined ? {} : { topoNames }),
|
|
659
1323
|
warnCount,
|
|
660
1324
|
};
|
|
661
1325
|
};
|
|
662
1326
|
|
|
1327
|
+
const formatPlainGuidanceLink = (link: WardenGuidanceLink): string => {
|
|
1328
|
+
const target = link.path ?? link.url;
|
|
1329
|
+
if (target === undefined || target === link.label) {
|
|
1330
|
+
return link.label;
|
|
1331
|
+
}
|
|
1332
|
+
return `${link.label} (${target})`;
|
|
1333
|
+
};
|
|
1334
|
+
|
|
663
1335
|
/**
|
|
664
1336
|
* Format the lint section of the report.
|
|
665
1337
|
*/
|
|
@@ -677,6 +1349,25 @@ const formatLintSection = (report: WardenReport): string[] => {
|
|
|
677
1349
|
lines.push(
|
|
678
1350
|
` ${d.filePath}:${String(d.line)} [${prefix}] ${d.rule} ${d.message}`
|
|
679
1351
|
);
|
|
1352
|
+
if (d.guidance !== undefined) {
|
|
1353
|
+
lines.push(` Next: ${d.guidance.summary}`);
|
|
1354
|
+
for (const [index, step] of (d.guidance.steps ?? []).entries()) {
|
|
1355
|
+
lines.push(` ${String(index + 1)}. ${step}`);
|
|
1356
|
+
}
|
|
1357
|
+
if (d.guidance.commands !== undefined) {
|
|
1358
|
+
lines.push(
|
|
1359
|
+
` Commands: ${d.guidance.commands.map((cmd) => `\`${cmd}\``).join(', ')}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
if (d.guidance.docs !== undefined) {
|
|
1363
|
+
lines.push(
|
|
1364
|
+
` Docs: ${d.guidance.docs.map(formatPlainGuidanceLink).join(', ')}`
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
if (d.guidance.relatedRules !== undefined) {
|
|
1368
|
+
lines.push(` Related: ${d.guidance.relatedRules.join(', ')}`);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
680
1371
|
}
|
|
681
1372
|
|
|
682
1373
|
return lines;
|
|
@@ -693,7 +1384,7 @@ const formatDriftSection = (drift: DriftResult | null): string[] => {
|
|
|
693
1384
|
return [`Drift: blocked (${drift.blockedReason})`, ''];
|
|
694
1385
|
}
|
|
695
1386
|
const label = drift.stale
|
|
696
|
-
? 'Drift: trails.lock is stale (regenerate with `trails topo
|
|
1387
|
+
? 'Drift: trails.lock is stale (regenerate with `trails topo compile`)'
|
|
697
1388
|
: 'Drift: clean';
|
|
698
1389
|
return [label, ''];
|
|
699
1390
|
};
|
|
@@ -709,6 +1400,9 @@ const formatResultLine = (report: WardenReport): string => {
|
|
|
709
1400
|
if (report.errorCount > 0) {
|
|
710
1401
|
parts.push(`${report.errorCount} errors`);
|
|
711
1402
|
}
|
|
1403
|
+
if (report.warnCount > 0 && report.effectiveConfig?.failOn === 'warning') {
|
|
1404
|
+
parts.push(`${report.warnCount} warnings`);
|
|
1405
|
+
}
|
|
712
1406
|
if (report.drift?.blockedReason !== undefined) {
|
|
713
1407
|
parts.push('established exports blocked');
|
|
714
1408
|
} else if (report.drift?.stale) {
|