@ontrails/warden 1.0.0-beta.23 → 1.0.0-beta.29
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 +113 -0
- package/README.md +35 -1
- package/package.json +10 -9
- package/src/ast.ts +109 -0
- package/src/cli.ts +44 -4
- package/src/command.ts +24 -34
- package/src/drift.ts +24 -8
- package/src/index.ts +9 -0
- package/src/project-rules.ts +290 -0
- package/src/resolve.ts +8 -7
- package/src/rules/ast.ts +837 -6
- package/src/rules/cli-command-route-coherence.ts +177 -0
- package/src/rules/composes-declarations.ts +210 -77
- package/src/rules/context-no-surface-types.ts +15 -12
- package/src/rules/contour-exists.ts +7 -7
- package/src/rules/dead-internal-trail.ts +11 -4
- package/src/rules/dead-public-trail.ts +258 -0
- package/src/rules/duplicate-public-contract.ts +91 -0
- package/src/rules/error-mapping-completeness.ts +5 -3
- package/src/rules/example-valid.ts +6 -12
- package/src/rules/fires-declarations.ts +41 -64
- package/src/rules/implementation-returns-result.ts +208 -76
- package/src/rules/incomplete-crud.ts +20 -19
- package/src/rules/index.ts +15 -0
- package/src/rules/layer-field-name-drift.ts +12 -6
- package/src/rules/library-projection-coherence.ts +100 -0
- package/src/rules/metadata.ts +102 -1
- package/src/rules/no-destructured-compose.ts +16 -14
- package/src/rules/no-native-error-result.ts +15 -8
- package/src/rules/no-redundant-result-error-wrap.ts +82 -29
- package/src/rules/no-sync-result-assumption.ts +70 -68
- package/src/rules/no-top-level-surface.ts +46 -64
- package/src/rules/on-references-exist.ts +1 -1
- package/src/rules/owner-projection-parity.ts +10 -13
- package/src/rules/public-export-example-coverage.ts +29 -20
- package/src/rules/public-internal-deep-imports.ts +2 -2
- package/src/rules/read-intent-fires.ts +8 -8
- package/src/rules/registry-names.ts +10 -0
- package/src/rules/resource-declarations.ts +20 -31
- package/src/rules/resource-exists.ts +2 -2
- package/src/rules/resource-id-grammar.ts +2 -2
- package/src/rules/resource-mock-coverage.ts +2 -2
- package/src/rules/static-resource-accessor-preference.ts +26 -34
- package/src/rules/surface-facet-coherence.ts +21 -29
- package/src/rules/trail-fork-coaching.ts +616 -0
- package/src/rules/trail-versioning-source.ts +56 -78
- package/src/rules/types.ts +2 -0
- package/src/rules/unreachable-detour-shadowing.ts +16 -21
- package/src/rules/valid-describe-refs.ts +14 -12
- package/src/rules/warden-export-symmetry.ts +42 -35
- package/src/rules/warden-rules-use-ast.ts +168 -50
- package/src/trails/cli-command-route-coherence.trail.ts +47 -0
- package/src/trails/dead-public-trail.trail.ts +31 -0
- package/src/trails/duplicate-public-contract.trail.ts +47 -0
- package/src/trails/error-mapping-completeness.trail.ts +1 -0
- package/src/trails/index.ts +5 -0
- package/src/trails/library-projection-coherence.trail.ts +43 -0
- package/src/trails/schema.ts +4 -0
- package/src/trails/trail-fork-coaching.trail.ts +42 -0
- package/src/trails/warden-rules-use-ast.trail.ts +19 -0
- package/src/trails/wrap-rule.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,118 @@
|
|
|
1
1
|
# @ontrails/warden
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.29
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @ontrails/adapter-kit@1.0.0-beta.29
|
|
8
|
+
- @ontrails/cli@1.0.0-beta.29
|
|
9
|
+
- @ontrails/config@1.0.0-beta.29
|
|
10
|
+
- @ontrails/core@1.0.0-beta.29
|
|
11
|
+
- @ontrails/permits@1.0.0-beta.29
|
|
12
|
+
- @ontrails/store@1.0.0-beta.29
|
|
13
|
+
- @ontrails/topographer@1.0.0-beta.29
|
|
14
|
+
|
|
15
|
+
## 1.0.0-beta.28
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- @ontrails/adapter-kit@1.0.0-beta.28
|
|
20
|
+
- @ontrails/cli@1.0.0-beta.28
|
|
21
|
+
- @ontrails/config@1.0.0-beta.28
|
|
22
|
+
- @ontrails/core@1.0.0-beta.28
|
|
23
|
+
- @ontrails/permits@1.0.0-beta.28
|
|
24
|
+
- @ontrails/store@1.0.0-beta.28
|
|
25
|
+
- @ontrails/topographer@1.0.0-beta.28
|
|
26
|
+
|
|
27
|
+
## 1.0.0-beta.27
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- @ontrails/adapter-kit@1.0.0-beta.27
|
|
32
|
+
- @ontrails/cli@1.0.0-beta.27
|
|
33
|
+
- @ontrails/config@1.0.0-beta.27
|
|
34
|
+
- @ontrails/core@1.0.0-beta.27
|
|
35
|
+
- @ontrails/permits@1.0.0-beta.27
|
|
36
|
+
- @ontrails/store@1.0.0-beta.27
|
|
37
|
+
- @ontrails/topographer@1.0.0-beta.27
|
|
38
|
+
|
|
39
|
+
## 1.0.0-beta.26
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 1307568: Centralize Trails config module path conventions, move local config overrides to root `trails.config.local.*`, scaffold the matching gitignore entries, and load project-local Warden rules from `.trails/rules.ts` or `.trails/rules/`.
|
|
44
|
+
- ef09e46: Add shared Trails project-root discovery helpers and use them in Warden so nested
|
|
45
|
+
cwd invocations still load root `trails.config.*` and project-local
|
|
46
|
+
`.trails/rules*` governance.
|
|
47
|
+
- 38cd9d6: Add a shared Trails config file loader that treats `trails.config.ts` as the natural primary while supporting JSON, JSONC, YAML, and TOML peer formats. Release and Warden config loading now consume the same loader and local overrides can be authored as data files.
|
|
48
|
+
- f8403c4: Collapse normal topo compilation onto one root `trails.lock` envelope that embeds the TopoGraph, hash, and summary while keeping legacy `.trails/trails.lock` plus `.trails/topo.lock` readers for migration compatibility.
|
|
49
|
+
- ff48e41: Harden project-local rule loading: Warden now discovers `.trails/rules.ts` and direct `.trails/rules/*.ts` files only, reports duplicate project-local rule ids, and emits a migration diagnostic for the retired `trails/warden/rules` location.
|
|
50
|
+
- Updated dependencies [1307568]
|
|
51
|
+
- Updated dependencies [ef09e46]
|
|
52
|
+
- Updated dependencies [38cd9d6]
|
|
53
|
+
- Updated dependencies [f8403c4]
|
|
54
|
+
- Updated dependencies [371d19e]
|
|
55
|
+
- @ontrails/config@1.0.0-beta.26
|
|
56
|
+
- @ontrails/core@1.0.0-beta.26
|
|
57
|
+
- @ontrails/topographer@1.0.0-beta.26
|
|
58
|
+
- @ontrails/adapter-kit@1.0.0-beta.26
|
|
59
|
+
- @ontrails/cli@1.0.0-beta.26
|
|
60
|
+
- @ontrails/permits@1.0.0-beta.26
|
|
61
|
+
- @ontrails/store@1.0.0-beta.26
|
|
62
|
+
|
|
63
|
+
## 1.0.0-beta.25
|
|
64
|
+
|
|
65
|
+
### Patch Changes
|
|
66
|
+
|
|
67
|
+
- a9fdbc7: Clarify surface accommodation doctrine in MCP surface facet metadata and Warden trail-fork coaching guidance.
|
|
68
|
+
- f8fd6ca: Add OXC Walker-backed AST facade helpers for parent-aware traversal, scope-aware traversal, source locations, and safe source edits.
|
|
69
|
+
- 0fcc42b: Add `dead-public-trail` and `duplicate-public-contract` Warden coaching rules so exported public trails stay anchored and duplicate surface contracts become visible drift.
|
|
70
|
+
- c36aca9: Preserve existing Result error boundaries directly and widen Warden pass-through
|
|
71
|
+
coaching beyond trail blazes.
|
|
72
|
+
- f556559: Adds curated typed AST node guards to the public `@ontrails/warden/ast` helper
|
|
73
|
+
surface so source consumers can narrow common OXC node shapes without assertion
|
|
74
|
+
casts.
|
|
75
|
+
- 6250729: Expands the public AST guard/accessor surface and migrates Warden/Regrade AST
|
|
76
|
+
consumers onto the typed helpers instead of rule-local node-field casts.
|
|
77
|
+
- d73c38e: Warn when Warden rules add raw AST node-field casts where a typed accessor exists.
|
|
78
|
+
- a8e4dc3: Clean up the Wayfinder navigation grammar before RC, including explicit pattern/query/file selectors, target-bound dependency and impact flags, drift-first provenance fields, stricter fires declaration diagnostics, and updated operator dogfood coverage.
|
|
79
|
+
- a4f9cf6: Reserve the `shift` error category and `WorkspaceShiftError` before the stable
|
|
80
|
+
cutover so surface mappings can distinguish moved-workspace retry verdicts.
|
|
81
|
+
Update Warden's error-mapping completeness examples to cover the reserved
|
|
82
|
+
category.
|
|
83
|
+
- 00c0cf8: Add Warden governance for CLI command route and alias coherence.
|
|
84
|
+
- b313c58: Add library projection coherence governance so Warden reports generated library export collisions and stale projection targets before packages materialize.
|
|
85
|
+
- f245fa0: Add advisory trail-fork coaching so Warden can warn when a trail may be hiding
|
|
86
|
+
several capabilities behind one branching action or operation input.
|
|
87
|
+
- f1e6efa: Recognize module-local helper functions that receive the trail context when checking declared compose usage.
|
|
88
|
+
- caff950: `implementation-returns-result` now recognizes conditional returns whose branches are all recognized Result expressions — both `return cond ? Result.err(...) : Result.ok(...)` statements (including branches that are Result helpers or Result-bound variables) and concise ternary blaze bodies. Previously the idiomatic two-branch ternary was flagged as an error.
|
|
89
|
+
- df13faf: Tighten Wayfinder navigation review findings: carry adapter filters into included adapter facts, keep live-source outline off the operator MCP surface until host-root binding exists, make unknown MCP resources protocol errors, route secondary Wayfinder graph populations through unified flags, and keep undeclared string `ctx.compose()` calls as Warden errors.
|
|
90
|
+
- Updated dependencies [c36aca9]
|
|
91
|
+
- Updated dependencies [3befcf1]
|
|
92
|
+
- Updated dependencies [f1e6efa]
|
|
93
|
+
- Updated dependencies [a4f9cf6]
|
|
94
|
+
- Updated dependencies [9bcf34e]
|
|
95
|
+
- Updated dependencies [f7d97fc]
|
|
96
|
+
- Updated dependencies [59d10da]
|
|
97
|
+
- Updated dependencies [d9c6e50]
|
|
98
|
+
- @ontrails/core@1.0.0-beta.25
|
|
99
|
+
- @ontrails/topographer@1.0.0-beta.25
|
|
100
|
+
- @ontrails/cli@1.0.0-beta.25
|
|
101
|
+
- @ontrails/adapter-kit@1.0.0-beta.25
|
|
102
|
+
- @ontrails/permits@1.0.0-beta.25
|
|
103
|
+
- @ontrails/store@1.0.0-beta.25
|
|
104
|
+
|
|
105
|
+
## 1.0.0-beta.24
|
|
106
|
+
|
|
107
|
+
### Patch Changes
|
|
108
|
+
|
|
109
|
+
- @ontrails/adapter-kit@1.0.0-beta.24
|
|
110
|
+
- @ontrails/cli@1.0.0-beta.24
|
|
111
|
+
- @ontrails/core@1.0.0-beta.24
|
|
112
|
+
- @ontrails/permits@1.0.0-beta.24
|
|
113
|
+
- @ontrails/store@1.0.0-beta.24
|
|
114
|
+
- @ontrails/topographer@1.0.0-beta.24
|
|
115
|
+
|
|
3
116
|
## 1.0.0-beta.23
|
|
4
117
|
|
|
5
118
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -37,6 +37,39 @@ Rules cover several families:
|
|
|
37
37
|
|
|
38
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
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
|
+
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.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export const rule = {
|
|
54
|
+
name: 'local-contract-check',
|
|
55
|
+
severity: 'error',
|
|
56
|
+
description: 'Local contract examples keep their migration marker.',
|
|
57
|
+
check(sourceCode, filePath) {
|
|
58
|
+
return sourceCode.includes('deprecatedMarker')
|
|
59
|
+
? [
|
|
60
|
+
{
|
|
61
|
+
filePath,
|
|
62
|
+
line: 1,
|
|
63
|
+
message: 'Replace deprecatedMarker before release.',
|
|
64
|
+
rule: 'local-contract-check',
|
|
65
|
+
severity: 'error',
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
: [];
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
40
73
|
## Drift detection
|
|
41
74
|
|
|
42
75
|
Warden integrates with `@ontrails/topographer` to detect when the topo has changed without updating the lock file:
|
|
@@ -124,12 +157,13 @@ This is the same factory used internally to build all built-in rule trails.
|
|
|
124
157
|
| `wardenTopo` | `Topo` of all built-in rule trails (one per rule) |
|
|
125
158
|
| `runWardenTrails(filePath, sourceCode, options?)` | Dispatch file-scoped rule trails for a file, collect diagnostics |
|
|
126
159
|
| `runTopoAwareWardenTrails(topo)` | Dispatch built-in topo-aware rule trails once for a resolved topo |
|
|
160
|
+
| `loadProjectWardenRules(rootDir)` | Load rule modules from `.trails/rules.ts` or direct `.trails/rules/*.ts` children |
|
|
127
161
|
| `formatGitHubAnnotations(report)` | GitHub Actions annotation format |
|
|
128
162
|
| `formatJson(report)` | Machine-readable JSON |
|
|
129
163
|
| `formatSummary(report)` | Compact summary line |
|
|
130
164
|
| `wrapRule(rule)` | Wrap a custom rule as a trail (same factory used for all built-in rule trails) |
|
|
131
165
|
|
|
132
|
-
AST parser helpers are exported from `@ontrails/warden/ast`, not the root runtime barrel. The stable authoring surface includes `parse`, `walk`, `walkScope`, `offsetToLine`, `findTrailDefinitions`, `findBlazeBodies`, `findContourDefinitions`, `isBlazeCall`, and string-literal helpers.
|
|
166
|
+
AST parser helpers are exported from `@ontrails/warden/ast`, not the root runtime barrel. The stable authoring surface includes `parse`, `walk`, `walkScope`, `walkWithParents`, `walkWithScopeContext`, `offsetToLine`, `offsetToLineColumn`, source-edit helpers, `findTrailDefinitions`, `findBlazeBodies`, `findContourDefinitions`, `isBlazeCall`, and string-literal helpers.
|
|
133
167
|
|
|
134
168
|
`runWarden({ tier })` can narrow a run to `source-static`, `project-static`, `topo-aware`, `drift`, or `advisory`. Omit `tier` for the default full run.
|
|
135
169
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/warden",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.29",
|
|
4
4
|
"bin": {
|
|
5
5
|
"warden": "./bin/warden.ts"
|
|
6
6
|
},
|
|
@@ -28,20 +28,21 @@
|
|
|
28
28
|
"clean": "rm -rf dist *.tsbuildinfo"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@ontrails/adapter-kit": "^1.0.0-beta.
|
|
32
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
33
|
-
"@ontrails/
|
|
34
|
-
"@ontrails/
|
|
31
|
+
"@ontrails/adapter-kit": "^1.0.0-beta.29",
|
|
32
|
+
"@ontrails/cli": "^1.0.0-beta.29",
|
|
33
|
+
"@ontrails/config": "^1.0.0-beta.29",
|
|
34
|
+
"@ontrails/permits": "^1.0.0-beta.29",
|
|
35
|
+
"@ontrails/store": "^1.0.0-beta.29",
|
|
35
36
|
"oxc-parser": "^0.121.0",
|
|
36
37
|
"oxc-resolver": "11.19.1",
|
|
38
|
+
"oxc-walker": "^1.0.0",
|
|
37
39
|
"zod": "^4.3.5"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
|
-
"@ontrails/
|
|
41
|
-
"@ontrails/testing": "^1.0.0-beta.23"
|
|
42
|
+
"@ontrails/testing": "^1.0.0-beta.29"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
45
|
-
"@ontrails/topographer": "^1.0.0-beta.
|
|
45
|
+
"@ontrails/core": "^1.0.0-beta.29",
|
|
46
|
+
"@ontrails/topographer": "^1.0.0-beta.29"
|
|
46
47
|
}
|
|
47
48
|
}
|
package/src/ast.ts
CHANGED
|
@@ -10,19 +10,128 @@ export {
|
|
|
10
10
|
findContourDefinitions,
|
|
11
11
|
findStringLiterals,
|
|
12
12
|
findTrailDefinitions,
|
|
13
|
+
applySourceEdits,
|
|
14
|
+
createSourceEdit,
|
|
15
|
+
getNodeAlternate,
|
|
16
|
+
getNodeArgument,
|
|
17
|
+
getNodeArguments,
|
|
18
|
+
getNodeBody,
|
|
19
|
+
getNodeBodyNode,
|
|
20
|
+
getNodeBodyStatements,
|
|
21
|
+
getNodeCallee,
|
|
22
|
+
getNodeCases,
|
|
23
|
+
getNodeComputed,
|
|
24
|
+
getNodeConsequent,
|
|
25
|
+
getNodeDeclaration,
|
|
26
|
+
getNodeDeclarations,
|
|
27
|
+
getNodeDiscriminant,
|
|
28
|
+
getNodeElements,
|
|
29
|
+
getNodeExported,
|
|
30
|
+
getNodeExportKind,
|
|
31
|
+
getNodeExpression,
|
|
32
|
+
getNodeId,
|
|
33
|
+
getNodeImported,
|
|
34
|
+
getNodeInit,
|
|
35
|
+
getNodeKey,
|
|
36
|
+
getNodeKind,
|
|
37
|
+
getNodeLeft,
|
|
38
|
+
getNodeLocal,
|
|
39
|
+
getNodeName,
|
|
40
|
+
getNodeObject,
|
|
41
|
+
getNodeOperator,
|
|
42
|
+
getNodeParam,
|
|
43
|
+
getNodeParams,
|
|
44
|
+
getNodeProperties,
|
|
45
|
+
getNodeProperty,
|
|
46
|
+
getNodeReturnType,
|
|
47
|
+
getNodeRight,
|
|
48
|
+
getNodeSource,
|
|
49
|
+
getNodeSpecifiers,
|
|
50
|
+
getNodeSuperClass,
|
|
51
|
+
getNodeTest,
|
|
52
|
+
getNodeTypeAnnotation,
|
|
53
|
+
getNodeValue,
|
|
54
|
+
getNodeValueNode,
|
|
13
55
|
getStringValue,
|
|
56
|
+
identifierName,
|
|
57
|
+
isArrayExpression,
|
|
58
|
+
isAssignmentPattern,
|
|
59
|
+
isAstNode,
|
|
60
|
+
isBinaryExpression,
|
|
61
|
+
isBlockStatement,
|
|
14
62
|
isBlazeCall,
|
|
63
|
+
isCallExpression,
|
|
64
|
+
isClassMember,
|
|
65
|
+
isDeclarationWithId,
|
|
66
|
+
isExportAllDeclaration,
|
|
67
|
+
isExportDeclaration,
|
|
68
|
+
isExportDefaultDeclaration,
|
|
69
|
+
isExportNamedDeclaration,
|
|
70
|
+
isExportSpecifier,
|
|
71
|
+
isExpressionStatement,
|
|
72
|
+
isFunctionLike,
|
|
73
|
+
isIdentifier,
|
|
74
|
+
isImportDeclaration,
|
|
75
|
+
isImportSpecifier,
|
|
76
|
+
isMemberExpression,
|
|
77
|
+
isObjectExpression,
|
|
78
|
+
isProgram,
|
|
79
|
+
isProperty,
|
|
80
|
+
isRestElement,
|
|
81
|
+
isReturnStatement,
|
|
15
82
|
isStringLiteral,
|
|
83
|
+
isUnaryExpression,
|
|
84
|
+
isVariableDeclaration,
|
|
85
|
+
isVariableDeclarator,
|
|
86
|
+
offsetToLineColumn,
|
|
16
87
|
offsetToLine,
|
|
17
88
|
parse,
|
|
89
|
+
parseWithDiagnostics,
|
|
90
|
+
validateSourceEdits,
|
|
18
91
|
walk,
|
|
92
|
+
walkWithParents,
|
|
93
|
+
walkWithScopeContext,
|
|
19
94
|
walkScope,
|
|
20
95
|
} from './rules/ast.js';
|
|
21
96
|
export type {
|
|
97
|
+
ArrayExpressionNode,
|
|
98
|
+
AssignmentPatternNode,
|
|
99
|
+
AstFieldProjection,
|
|
100
|
+
AstParentContext,
|
|
22
101
|
AstNode,
|
|
102
|
+
AstParseDiagnostic,
|
|
103
|
+
AstParseDiagnosticLabel,
|
|
104
|
+
AstParseResult,
|
|
105
|
+
AstScopeContext,
|
|
106
|
+
AstScopeDeclaration,
|
|
107
|
+
BinaryExpressionNode,
|
|
108
|
+
BlockStatementNode,
|
|
109
|
+
CallExpressionNode,
|
|
110
|
+
ClassMemberNode,
|
|
23
111
|
ContourDefinition,
|
|
112
|
+
CuratedAstNode,
|
|
113
|
+
DeclarationWithIdNode,
|
|
114
|
+
ExportDeclarationNode,
|
|
115
|
+
ExportSpecifierNode,
|
|
116
|
+
ExpressionStatementNode,
|
|
24
117
|
FindContourDefinitionsOptions,
|
|
25
118
|
FrameworkNamespaceContext,
|
|
119
|
+
FunctionLikeNode,
|
|
120
|
+
IdentifierNode,
|
|
121
|
+
ImportDeclarationNode,
|
|
122
|
+
ImportSpecifierNode,
|
|
123
|
+
MemberExpressionNode,
|
|
124
|
+
ObjectExpressionNode,
|
|
125
|
+
ProgramNode,
|
|
126
|
+
PropertyNode,
|
|
127
|
+
RestElementNode,
|
|
128
|
+
ReturnStatementNode,
|
|
129
|
+
SourceEdit,
|
|
130
|
+
SourceLocation,
|
|
26
131
|
StringLiteralMatch,
|
|
132
|
+
StringLiteralNode,
|
|
27
133
|
TrailDefinition,
|
|
134
|
+
UnaryExpressionNode,
|
|
135
|
+
VariableDeclarationNode,
|
|
136
|
+
VariableDeclaratorNode,
|
|
28
137
|
} from './rules/ast.js';
|
package/src/cli.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
9
9
|
|
|
10
|
+
import { resolveTrailsProjectRoot } from '@ontrails/config';
|
|
10
11
|
import type { Topo } from '@ontrails/core';
|
|
11
12
|
import { deriveTopoGraph } from '@ontrails/topographer';
|
|
12
13
|
import type { TopoGraph } from '@ontrails/topographer';
|
|
@@ -27,16 +28,18 @@ import { isDraftMarkedFile } from './draft.js';
|
|
|
27
28
|
import { applySafeFixesToSource, hasSafeFixEdits } from './fix.js';
|
|
28
29
|
import type { DriftResult } from './drift.js';
|
|
29
30
|
import { checkDrift } from './drift.js';
|
|
31
|
+
import { loadProjectWardenRules } from './project-rules.js';
|
|
32
|
+
import type { ProjectWardenRules } from './project-rules.js';
|
|
30
33
|
import {
|
|
31
34
|
collectProjectDocumentationImportResolutions,
|
|
32
35
|
collectProjectImportResolutions,
|
|
33
36
|
collectPublicWorkspaces,
|
|
34
37
|
} from './project-context.js';
|
|
35
38
|
import {
|
|
39
|
+
collectComposeTargetTrailIds,
|
|
36
40
|
collectContourDefinitionIds,
|
|
37
41
|
collectContourReferenceTargetsByName,
|
|
38
42
|
collectCrudTableIds as collectCrudTableIdsFromAst,
|
|
39
|
-
collectComposeTargetTrailIds,
|
|
40
43
|
collectOnTargetSignalIds as collectOnTargetSignalIdsFromAst,
|
|
41
44
|
collectReconcileTableIds as collectReconcileTableIdsFromAst,
|
|
42
45
|
collectResourceDefinitionIds,
|
|
@@ -119,6 +122,15 @@ export interface WardenRunOptions {
|
|
|
119
122
|
* lint rule dispatch. `lintOnly` and `driftOnly` remain compatibility shims.
|
|
120
123
|
*/
|
|
121
124
|
readonly tier?: WardenRuleTier | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Load project-local Warden rules from `.trails/rules.ts` or `.trails/rules/`.
|
|
127
|
+
*
|
|
128
|
+
* Enabled by default for normal runs so a Trails app can carry migration or
|
|
129
|
+
* repo-local governance with the project instead of shipping it from
|
|
130
|
+
* `@ontrails/warden`. Set to `false` for narrow tests or embedders that need
|
|
131
|
+
* only built-in and explicitly provided rules.
|
|
132
|
+
*/
|
|
133
|
+
readonly projectRules?: boolean | undefined;
|
|
122
134
|
/**
|
|
123
135
|
* App topology for drift detection. When provided, enables real topology
|
|
124
136
|
* drift comparison and unlocks the topo-aware rule dispatch path.
|
|
@@ -324,6 +336,7 @@ interface MutableProjectContext {
|
|
|
324
336
|
knownResourceIds: Set<string>;
|
|
325
337
|
knownSignalIds: Set<string>;
|
|
326
338
|
knownTrailIds: Set<string>;
|
|
339
|
+
topoTrailIds: Set<string>;
|
|
327
340
|
importResolutionsByFile: Map<string, readonly WardenImportResolution[]>;
|
|
328
341
|
documentedImportResolutionsByFile: Map<
|
|
329
342
|
string,
|
|
@@ -352,6 +365,7 @@ const createMutableProjectContext = (): MutableProjectContext => ({
|
|
|
352
365
|
onTargetSignalIds: new Set<string>(),
|
|
353
366
|
publicWorkspaces: new Map(),
|
|
354
367
|
reconcileTableIds: new Set<string>(),
|
|
368
|
+
topoTrailIds: new Set<string>(),
|
|
355
369
|
trailIntentsById: new Map<string, 'destroy' | 'read' | 'write'>(),
|
|
356
370
|
});
|
|
357
371
|
|
|
@@ -401,6 +415,9 @@ const toProjectContext = (context: MutableProjectContext): ProjectContext => ({
|
|
|
401
415
|
knownResourceIds: context.knownResourceIds,
|
|
402
416
|
knownSignalIds: context.knownSignalIds,
|
|
403
417
|
knownTrailIds: context.knownTrailIds,
|
|
418
|
+
...(context.topoTrailIds.size > 0
|
|
419
|
+
? { topoTrailIds: context.topoTrailIds }
|
|
420
|
+
: {}),
|
|
404
421
|
...(context.importResolutionsByFile.size > 0
|
|
405
422
|
? { importResolutionsByFile: context.importResolutionsByFile }
|
|
406
423
|
: {}),
|
|
@@ -635,6 +652,7 @@ const collectTopoKnownIds = (
|
|
|
635
652
|
|
|
636
653
|
for (const id of appTopo.trails.keys()) {
|
|
637
654
|
context.knownTrailIds.add(id);
|
|
655
|
+
context.topoTrailIds.add(id);
|
|
638
656
|
}
|
|
639
657
|
|
|
640
658
|
for (const id of appTopo.resources.keys()) {
|
|
@@ -854,6 +872,23 @@ const createOptionsDiagnostic = (message: string): WardenDiagnostic => ({
|
|
|
854
872
|
severity: 'error',
|
|
855
873
|
});
|
|
856
874
|
|
|
875
|
+
const emptyProjectWardenRules = (): ProjectWardenRules => ({
|
|
876
|
+
diagnostics: [],
|
|
877
|
+
sourceRules: [],
|
|
878
|
+
topoRules: [],
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
const loadProjectRulesForRun = async (
|
|
882
|
+
rootDir: string,
|
|
883
|
+
options: WardenRunOptions,
|
|
884
|
+
runLint: boolean
|
|
885
|
+
): Promise<ProjectWardenRules> => {
|
|
886
|
+
if (!runLint || options.projectRules === false) {
|
|
887
|
+
return emptyProjectWardenRules();
|
|
888
|
+
}
|
|
889
|
+
return loadProjectWardenRules(rootDir);
|
|
890
|
+
};
|
|
891
|
+
|
|
857
892
|
interface WardenRuleSelector {
|
|
858
893
|
readonly depth?: WardenDepth | undefined;
|
|
859
894
|
readonly tier?: WardenRuleTier | undefined;
|
|
@@ -1411,7 +1446,10 @@ export const applySafeFixesToFiles = async (
|
|
|
1411
1446
|
export const runWarden = async (
|
|
1412
1447
|
options: WardenRunOptions = {}
|
|
1413
1448
|
): Promise<WardenReport> => {
|
|
1414
|
-
const rootDir =
|
|
1449
|
+
const { rootDir } = resolveTrailsProjectRoot({
|
|
1450
|
+
explicitRootDir: options.rootDir,
|
|
1451
|
+
startDir: process.cwd(),
|
|
1452
|
+
});
|
|
1415
1453
|
const { diagnostics: configDiagnostics, effectiveConfig } =
|
|
1416
1454
|
resolveWardenConfig({
|
|
1417
1455
|
cli: buildCliConfigLayer(options),
|
|
@@ -1433,13 +1471,14 @@ export const runWarden = async (
|
|
|
1433
1471
|
} satisfies WardenRuleSelector;
|
|
1434
1472
|
const runLint = shouldRunLint(options);
|
|
1435
1473
|
const runDrift = shouldRunDrift(options, effectiveConfig);
|
|
1474
|
+
const projectRules = await loadProjectRulesForRun(rootDir, options, runLint);
|
|
1436
1475
|
const lintResult = runLint
|
|
1437
1476
|
? await lintFiles(
|
|
1438
1477
|
rootDir,
|
|
1439
1478
|
effectiveConfig.drafts,
|
|
1440
1479
|
topoTargets,
|
|
1441
|
-
options.extraTopoRules ?? [],
|
|
1442
|
-
options.extraSourceRules ?? [],
|
|
1480
|
+
[...projectRules.topoRules, ...(options.extraTopoRules ?? [])],
|
|
1481
|
+
[...projectRules.sourceRules, ...(options.extraSourceRules ?? [])],
|
|
1443
1482
|
selector
|
|
1444
1483
|
)
|
|
1445
1484
|
: { diagnostics: [], sourceFiles: [] };
|
|
@@ -1447,6 +1486,7 @@ export const runWarden = async (
|
|
|
1447
1486
|
|
|
1448
1487
|
const rawDiagnostics = [
|
|
1449
1488
|
...configDiagnostics,
|
|
1489
|
+
...projectRules.diagnostics,
|
|
1450
1490
|
...optionDiagnostics,
|
|
1451
1491
|
...lintResult.diagnostics,
|
|
1452
1492
|
...adapterDiagnostics,
|
package/src/command.ts
CHANGED
|
@@ -14,6 +14,10 @@ import type {
|
|
|
14
14
|
} from '@ontrails/cli';
|
|
15
15
|
import type { Topo } from '@ontrails/core';
|
|
16
16
|
import { AmbiguousError, NotFoundError } from '@ontrails/core';
|
|
17
|
+
import {
|
|
18
|
+
loadTrailsConfigValue,
|
|
19
|
+
resolveTrailsProjectRoot,
|
|
20
|
+
} from '@ontrails/config';
|
|
17
21
|
|
|
18
22
|
import type {
|
|
19
23
|
WardenConfigInput,
|
|
@@ -57,13 +61,6 @@ interface MutableWardenConfigLayer {
|
|
|
57
61
|
noLockMutation?: boolean | undefined;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
const CONFIG_CANDIDATES = [
|
|
61
|
-
'trails.config.ts',
|
|
62
|
-
'trails.config.mts',
|
|
63
|
-
'trails.config.js',
|
|
64
|
-
'trails.config.mjs',
|
|
65
|
-
] as const;
|
|
66
|
-
|
|
67
64
|
const diagnostic = ({
|
|
68
65
|
filePath = '<warden-cli>',
|
|
69
66
|
message,
|
|
@@ -176,7 +173,7 @@ interface WardenValueAliasTarget {
|
|
|
176
173
|
readonly configKey: AliasConfigKey;
|
|
177
174
|
}
|
|
178
175
|
|
|
179
|
-
const wardenAliasSpecs = [
|
|
176
|
+
const wardenAliasSpecs: readonly WardenAliasSpec[] = [
|
|
180
177
|
{
|
|
181
178
|
aliases: true,
|
|
182
179
|
choices: wardenFormatValues,
|
|
@@ -203,7 +200,7 @@ const wardenAliasSpecs = [
|
|
|
203
200
|
configKey: 'drafts',
|
|
204
201
|
flagName: 'drafts',
|
|
205
202
|
},
|
|
206
|
-
]
|
|
203
|
+
];
|
|
207
204
|
|
|
208
205
|
const wardenValueAliasTargets: readonly WardenValueAliasTarget[] =
|
|
209
206
|
wardenAliasSpecs.flatMap((spec) =>
|
|
@@ -491,12 +488,7 @@ const findConfigPath = (
|
|
|
491
488
|
};
|
|
492
489
|
}
|
|
493
490
|
|
|
494
|
-
|
|
495
|
-
resolve(rootDir, entry)
|
|
496
|
-
).find((entry) => existsSync(entry));
|
|
497
|
-
return candidate === undefined
|
|
498
|
-
? { diagnostics: [] }
|
|
499
|
-
: { configPath: candidate, diagnostics: [] };
|
|
491
|
+
return { diagnostics: [] };
|
|
500
492
|
};
|
|
501
493
|
|
|
502
494
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
@@ -529,14 +521,6 @@ const extractWardenConfig = (value: unknown): WardenConfigInput | undefined =>
|
|
|
529
521
|
? (value['warden'] as WardenConfigInput)
|
|
530
522
|
: undefined;
|
|
531
523
|
|
|
532
|
-
const importConfigModule = async (
|
|
533
|
-
configPath: string
|
|
534
|
-
): Promise<Record<string, unknown>> => {
|
|
535
|
-
const url = pathToFileURL(configPath);
|
|
536
|
-
url.searchParams.set('t', Date.now().toString());
|
|
537
|
-
return (await import(url.href)) as Record<string, unknown>;
|
|
538
|
-
};
|
|
539
|
-
|
|
540
524
|
export const loadWardenConfig = async ({
|
|
541
525
|
configPath,
|
|
542
526
|
env = {},
|
|
@@ -547,29 +531,32 @@ export const loadWardenConfig = async ({
|
|
|
547
531
|
readonly rootDir: string;
|
|
548
532
|
}): Promise<WardenConfigLoadResult> => {
|
|
549
533
|
const located = findConfigPath(rootDir, configPath);
|
|
550
|
-
if (located.configPath === undefined) {
|
|
534
|
+
if (configPath !== undefined && located.configPath === undefined) {
|
|
551
535
|
return located;
|
|
552
536
|
}
|
|
553
537
|
|
|
554
538
|
try {
|
|
555
|
-
const
|
|
556
|
-
const exported =
|
|
539
|
+
const loaded = await loadTrailsConfigValue({ configPath, rootDir });
|
|
540
|
+
const exported = loaded.value;
|
|
541
|
+
if (exported === undefined) {
|
|
542
|
+
return located;
|
|
543
|
+
}
|
|
557
544
|
if (isResolvableConfig(exported)) {
|
|
558
545
|
const resolved = await exported.resolve({ cwd: rootDir, env });
|
|
559
546
|
if (isResultLike(resolved)) {
|
|
560
547
|
if (resolved.isOk()) {
|
|
561
548
|
return {
|
|
562
549
|
config: extractWardenConfig(resolved.value),
|
|
563
|
-
configPath:
|
|
550
|
+
configPath: loaded.configPath,
|
|
564
551
|
diagnostics: located.diagnostics,
|
|
565
552
|
};
|
|
566
553
|
}
|
|
567
554
|
return {
|
|
568
|
-
configPath:
|
|
555
|
+
configPath: loaded.configPath,
|
|
569
556
|
diagnostics: [
|
|
570
557
|
...located.diagnostics,
|
|
571
558
|
diagnostic({
|
|
572
|
-
filePath:
|
|
559
|
+
filePath: loaded.configPath ?? rootDir,
|
|
573
560
|
message: `Failed to resolve Warden config: ${errorMessage(resolved.error)}`,
|
|
574
561
|
rule: 'warden-config',
|
|
575
562
|
}),
|
|
@@ -578,22 +565,22 @@ export const loadWardenConfig = async ({
|
|
|
578
565
|
}
|
|
579
566
|
return {
|
|
580
567
|
config: extractWardenConfig(resolved),
|
|
581
|
-
configPath:
|
|
568
|
+
configPath: loaded.configPath,
|
|
582
569
|
diagnostics: located.diagnostics,
|
|
583
570
|
};
|
|
584
571
|
}
|
|
585
572
|
return {
|
|
586
573
|
config: extractWardenConfig(exported),
|
|
587
|
-
configPath:
|
|
574
|
+
configPath: loaded.configPath,
|
|
588
575
|
diagnostics: located.diagnostics,
|
|
589
576
|
};
|
|
590
577
|
} catch (error) {
|
|
591
578
|
return {
|
|
592
|
-
configPath: located.configPath,
|
|
579
|
+
configPath: located.configPath ?? configPath,
|
|
593
580
|
diagnostics: [
|
|
594
581
|
...located.diagnostics,
|
|
595
582
|
diagnostic({
|
|
596
|
-
filePath: located.configPath,
|
|
583
|
+
filePath: located.configPath ?? rootDir,
|
|
597
584
|
message: `Failed to load Warden config: ${errorMessage(error)}`,
|
|
598
585
|
rule: 'warden-config',
|
|
599
586
|
}),
|
|
@@ -906,7 +893,10 @@ export const runWardenCommand = async ({
|
|
|
906
893
|
env = {},
|
|
907
894
|
}: RunWardenCommandOptions): Promise<WardenCommandResult> => {
|
|
908
895
|
const parsed = parseWardenCommandArgs(args);
|
|
909
|
-
const rootDir =
|
|
896
|
+
const { rootDir } = resolveTrailsProjectRoot({
|
|
897
|
+
explicitRootDir: parsed.rootDir,
|
|
898
|
+
startDir: cwd,
|
|
899
|
+
});
|
|
910
900
|
const loadedConfig = await loadWardenConfig({
|
|
911
901
|
configPath: parsed.configPath,
|
|
912
902
|
env,
|
package/src/drift.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Trails lock drift detection.
|
|
3
3
|
*
|
|
4
|
-
* Compares the `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Compares the committed `trails.lock` TopoGraph hash against a freshly
|
|
5
|
+
* generated TopoGraph hash to detect when the trail topology changed without
|
|
6
|
+
* updating the committed resolved truth.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { existsSync, statSync } from 'node:fs';
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
deriveTopoGraphHash,
|
|
21
21
|
isTopoArtifactRegenerationError,
|
|
22
22
|
readLockManifest,
|
|
23
|
+
readTrailsLock,
|
|
23
24
|
} from '@ontrails/topographer';
|
|
24
25
|
import type { LockManifest } from '@ontrails/topographer';
|
|
25
26
|
|
|
@@ -57,11 +58,26 @@ const blockedLockRead = (reason: string): BlockedLockRead => ({
|
|
|
57
58
|
const readCommittedLockManifest = async (
|
|
58
59
|
rootDir: string
|
|
59
60
|
): Promise<BlockedLockRead | LockManifest | null> => {
|
|
60
|
-
const trailsDir = deriveTrailsDir({ rootDir });
|
|
61
61
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if (!(existsSync(rootDir) && statSync(rootDir).isDirectory())) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const rootLock = await readTrailsLock({ dir: rootDir });
|
|
66
|
+
if (rootLock !== null) {
|
|
67
|
+
return {
|
|
68
|
+
artifacts: [
|
|
69
|
+
{
|
|
70
|
+
path: 'topo.lock',
|
|
71
|
+
role: 'topo',
|
|
72
|
+
sha256: rootLock.topoGraphHash,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
scope: rootLock.scope,
|
|
76
|
+
summary: rootLock.summary,
|
|
77
|
+
version: 3,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return await readLockManifest({ dir: deriveTrailsDir({ rootDir }) });
|
|
65
81
|
} catch (error) {
|
|
66
82
|
if (isTopoArtifactRegenerationError(error)) {
|
|
67
83
|
return blockedLockRead(error.message);
|