@ontrails/warden 1.0.0-beta.32 → 1.0.0-beta.39
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 +56 -0
- package/README.md +1 -1
- package/package.json +9 -9
- package/src/cli.ts +120 -10
- package/src/command.ts +25 -5
- package/src/drift.ts +116 -7
- package/src/fix.ts +8 -2
- package/src/formatters.ts +3 -8
- package/src/index.ts +30 -2
- package/src/project-context.ts +255 -1
- package/src/rules/ast.ts +4 -0
- package/src/rules/duplicate-exported-symbol.ts +211 -0
- package/src/rules/duplicate-public-contract.ts +2 -1
- package/src/rules/governed-symbol-residue.ts +131 -0
- package/src/rules/index.ts +43 -3
- package/src/rules/metadata.ts +105 -11
- package/src/rules/no-legacy-cli-alias-export.ts +247 -0
- package/src/rules/no-retired-cross-vocabulary.ts +19 -9
- package/src/rules/public-export-example-coverage.ts +5 -0
- package/src/rules/registry-names.ts +12 -2
- package/src/rules/retired-vocabulary.ts +396 -0
- package/src/rules/signal-graph-coaching.ts +32 -3
- package/src/rules/surface-overlay-coherence.ts +262 -0
- package/src/rules/{surface-facet-coherence.ts → surface-trailhead-coherence.ts} +45 -41
- package/src/rules/trailhead-override-divergence.ts +356 -0
- package/src/rules/types.ts +58 -1
- package/src/rules/webhook-route-collision.ts +64 -1
- package/src/trails/duplicate-exported-symbol.trail.ts +48 -0
- package/src/trails/duplicate-public-contract.trail.ts +1 -1
- package/src/trails/governed-symbol-residue.trail.ts +24 -0
- package/src/trails/index.ts +6 -1
- package/src/trails/no-legacy-cli-alias-export.trail.ts +41 -0
- package/src/trails/schema.ts +39 -0
- package/src/trails/surface-overlay-coherence.trail.ts +24 -0
- package/src/trails/{surface-facet-coherence.trail.ts → surface-trailhead-coherence.trail.ts} +4 -4
- package/src/trails/trailhead-override-divergence.trail.ts +47 -0
- package/src/trails/wrap-rule.ts +10 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
identifierName,
|
|
3
|
+
offsetToLine,
|
|
4
|
+
parseWithDiagnostics,
|
|
5
|
+
walk,
|
|
6
|
+
} from './ast.js';
|
|
7
|
+
import type { AstNode } from './ast.js';
|
|
8
|
+
import { listGovernedVocabularyTransitions } from './retired-vocabulary.js';
|
|
9
|
+
import type {
|
|
10
|
+
GovernedVocabularySymbolRename,
|
|
11
|
+
GovernedVocabularyTransition,
|
|
12
|
+
} from './retired-vocabulary.js';
|
|
13
|
+
import type { WardenDiagnostic, WardenFix, WardenRule } from './types.js';
|
|
14
|
+
|
|
15
|
+
const RULE_NAME = 'governed-symbol-residue';
|
|
16
|
+
|
|
17
|
+
const ACTIVE_STATUSES = new Set<GovernedVocabularyTransition['status']>([
|
|
18
|
+
'active',
|
|
19
|
+
'complete',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const FIX_OWNED_TRANSITION_IDS = new Set(['cross-compose']);
|
|
23
|
+
|
|
24
|
+
const ALLOWED_SOURCE_PATH_SUFFIXES = [
|
|
25
|
+
'/packages/warden/src/rules/governed-symbol-residue.ts',
|
|
26
|
+
'/packages/warden/src/rules/retired-vocabulary.ts',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
const normalizePath = (filePath: string): string =>
|
|
30
|
+
filePath.replaceAll('\\', '/');
|
|
31
|
+
|
|
32
|
+
const isAllowedSourcePath = (filePath: string): boolean =>
|
|
33
|
+
ALLOWED_SOURCE_PATH_SUFFIXES.some((suffix) =>
|
|
34
|
+
normalizePath(filePath).endsWith(suffix)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const activeSymbolRenames = (): readonly GovernedVocabularySymbolRename[] =>
|
|
38
|
+
listGovernedVocabularyTransitions()
|
|
39
|
+
.filter(
|
|
40
|
+
(transition) =>
|
|
41
|
+
ACTIVE_STATUSES.has(transition.status) &&
|
|
42
|
+
!FIX_OWNED_TRANSITION_IDS.has(transition.id)
|
|
43
|
+
)
|
|
44
|
+
.flatMap((transition) => transition.symbolRenames);
|
|
45
|
+
|
|
46
|
+
const renameBySourceSymbol = (): ReadonlyMap<
|
|
47
|
+
string,
|
|
48
|
+
GovernedVocabularySymbolRename
|
|
49
|
+
> =>
|
|
50
|
+
new Map(
|
|
51
|
+
activeSymbolRenames().map((rename) => [rename.from, rename] as const)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const safeFixFor = (
|
|
55
|
+
sourceCode: string,
|
|
56
|
+
node: AstNode,
|
|
57
|
+
rename: GovernedVocabularySymbolRename
|
|
58
|
+
): WardenFix | undefined => {
|
|
59
|
+
const end = node.start + rename.from.length;
|
|
60
|
+
if (sourceCode.slice(node.start, end) !== rename.from) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
class: 'term-rewrite',
|
|
65
|
+
edits: [
|
|
66
|
+
{
|
|
67
|
+
end,
|
|
68
|
+
replacement: rename.to,
|
|
69
|
+
start: node.start,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
reason: `Retired governed symbol '${rename.from}' has a mechanical replacement '${rename.to}'.`,
|
|
73
|
+
safety: 'safe',
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const diagnosticFor = (
|
|
78
|
+
sourceCode: string,
|
|
79
|
+
filePath: string,
|
|
80
|
+
node: AstNode,
|
|
81
|
+
rename: GovernedVocabularySymbolRename
|
|
82
|
+
): WardenDiagnostic => {
|
|
83
|
+
const fix = safeFixFor(sourceCode, node, rename);
|
|
84
|
+
return {
|
|
85
|
+
filePath,
|
|
86
|
+
...(fix === undefined ? {} : { fix }),
|
|
87
|
+
line: offsetToLine(sourceCode, node.start),
|
|
88
|
+
message: `Retired governed symbol '${rename.from}' should migrate to '${rename.to}'.`,
|
|
89
|
+
rule: RULE_NAME,
|
|
90
|
+
severity: 'error',
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const governedSymbolResidue: WardenRule = {
|
|
95
|
+
check(sourceCode: string, filePath: string): readonly WardenDiagnostic[] {
|
|
96
|
+
if (isAllowedSourcePath(filePath)) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const renames = renameBySourceSymbol();
|
|
101
|
+
if (renames.size === 0) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const parsed = parseWithDiagnostics(filePath, sourceCode);
|
|
106
|
+
if (!parsed.ast || parsed.diagnostics.length > 0) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const diagnostics: WardenDiagnostic[] = [];
|
|
111
|
+
walk(parsed.ast, (node) => {
|
|
112
|
+
const name = identifierName(node);
|
|
113
|
+
if (name === null) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const rename = renames.get(name);
|
|
118
|
+
if (rename === undefined) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
diagnostics.push(diagnosticFor(sourceCode, filePath, node, rename));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return diagnostics;
|
|
126
|
+
},
|
|
127
|
+
description:
|
|
128
|
+
'Detect active governed vocabulary symbols that remain in source code.',
|
|
129
|
+
name: RULE_NAME,
|
|
130
|
+
severity: 'error',
|
|
131
|
+
};
|
package/src/rules/index.ts
CHANGED
|
@@ -8,10 +8,12 @@ import { deadInternalTrail } from './dead-internal-trail.js';
|
|
|
8
8
|
import { deadPublicTrail } from './dead-public-trail.js';
|
|
9
9
|
import { draftFileMarking } from './draft-file-marking.js';
|
|
10
10
|
import { draftVisibleDebt } from './draft-visible-debt.js';
|
|
11
|
+
import { duplicateExportedSymbol } from './duplicate-exported-symbol.js';
|
|
11
12
|
import { duplicatePublicContract } from './duplicate-public-contract.js';
|
|
12
13
|
import { errorMappingCompleteness } from './error-mapping-completeness.js';
|
|
13
14
|
import { exampleValid } from './example-valid.js';
|
|
14
15
|
import { firesDeclarations } from './fires-declarations.js';
|
|
16
|
+
import { governedSymbolResidue } from './governed-symbol-residue.js';
|
|
15
17
|
import { implementationReturnsResult } from './implementation-returns-result.js';
|
|
16
18
|
import { incompleteAccessorForStandardOp } from './incomplete-accessor-for-standard-op.js';
|
|
17
19
|
import { incompleteCrud } from './incomplete-crud.js';
|
|
@@ -22,6 +24,7 @@ import { missingVisibility } from './missing-visibility.js';
|
|
|
22
24
|
import { missingReconcile } from './missing-reconcile.js';
|
|
23
25
|
import { noDevPermitInSource } from './no-dev-permit-in-source.js';
|
|
24
26
|
import { noDestructuredCompose } from './no-destructured-compose.js';
|
|
27
|
+
import { noLegacyCliAliasExport } from './no-legacy-cli-alias-export.js';
|
|
25
28
|
import { noLegacyLayerImports } from './no-legacy-layer-imports.js';
|
|
26
29
|
import { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
27
30
|
import { noNativeErrorResult } from './no-native-error-result.js';
|
|
@@ -50,8 +53,10 @@ import { resourceMockCoverage } from './resource-mock-coverage.js';
|
|
|
50
53
|
import { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
|
|
51
54
|
import { signalGraphCoaching } from './signal-graph-coaching.js';
|
|
52
55
|
import { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
|
|
53
|
-
import {
|
|
56
|
+
import { surfaceOverlayCoherence } from './surface-overlay-coherence.js';
|
|
57
|
+
import { surfaceTrailheadCoherence } from './surface-trailhead-coherence.js';
|
|
54
58
|
import { trailForkCoaching } from './trail-fork-coaching.js';
|
|
59
|
+
import { trailheadOverrideDivergence } from './trailhead-override-divergence.js';
|
|
55
60
|
import {
|
|
56
61
|
forkWithoutPreservedBlaze,
|
|
57
62
|
markerSchemaUnsupported,
|
|
@@ -81,6 +86,7 @@ export type {
|
|
|
81
86
|
WardenFixSafety,
|
|
82
87
|
WardenGuidance,
|
|
83
88
|
WardenGuidanceLink,
|
|
89
|
+
WardenExportedSymbolDefinition,
|
|
84
90
|
WardenRuleLifecycle,
|
|
85
91
|
WardenRuleLifecycleState,
|
|
86
92
|
WardenRuleConcern,
|
|
@@ -95,6 +101,30 @@ export type {
|
|
|
95
101
|
WardenSeverity,
|
|
96
102
|
} from './types.js';
|
|
97
103
|
|
|
104
|
+
export {
|
|
105
|
+
formatGovernedVocabularyTransitionGuide,
|
|
106
|
+
getGovernedVocabularyTransition,
|
|
107
|
+
governedVocabularyLiteralRenameSchema,
|
|
108
|
+
governedVocabularyPreserveRuleSchema,
|
|
109
|
+
governedVocabularyRegistrySchema,
|
|
110
|
+
governedVocabularyScopeSchema,
|
|
111
|
+
governedVocabularySymbolRenameSchema,
|
|
112
|
+
governedVocabularyTargetSchema,
|
|
113
|
+
governedVocabularyTransitionSchema,
|
|
114
|
+
governedVocabularyTransitionStatuses,
|
|
115
|
+
governedVocabularyTransitions,
|
|
116
|
+
listGovernedVocabularyTransitions,
|
|
117
|
+
requireGovernedVocabularyTransition,
|
|
118
|
+
} from './retired-vocabulary.js';
|
|
119
|
+
export type {
|
|
120
|
+
GovernedVocabularyLiteralRename,
|
|
121
|
+
GovernedVocabularyPreserveRule,
|
|
122
|
+
GovernedVocabularyScope,
|
|
123
|
+
GovernedVocabularySymbolRename,
|
|
124
|
+
GovernedVocabularyTarget,
|
|
125
|
+
GovernedVocabularyTransition,
|
|
126
|
+
GovernedVocabularyTransitionInput,
|
|
127
|
+
} from './retired-vocabulary.js';
|
|
98
128
|
export {
|
|
99
129
|
builtinWardenRuleMetadata,
|
|
100
130
|
getWardenRuleMetadata,
|
|
@@ -119,10 +149,12 @@ export { deadInternalTrail } from './dead-internal-trail.js';
|
|
|
119
149
|
export { deadPublicTrail } from './dead-public-trail.js';
|
|
120
150
|
export { draftFileMarking } from './draft-file-marking.js';
|
|
121
151
|
export { draftVisibleDebt } from './draft-visible-debt.js';
|
|
152
|
+
export { duplicateExportedSymbol } from './duplicate-exported-symbol.js';
|
|
122
153
|
export { duplicatePublicContract } from './duplicate-public-contract.js';
|
|
123
154
|
export { errorMappingCompleteness } from './error-mapping-completeness.js';
|
|
124
155
|
export { exampleValid } from './example-valid.js';
|
|
125
156
|
export { firesDeclarations } from './fires-declarations.js';
|
|
157
|
+
export { governedSymbolResidue } from './governed-symbol-residue.js';
|
|
126
158
|
export { incompleteAccessorForStandardOp } from './incomplete-accessor-for-standard-op.js';
|
|
127
159
|
export { incompleteCrud } from './incomplete-crud.js';
|
|
128
160
|
export { intentPropagation } from './intent-propagation.js';
|
|
@@ -133,6 +165,7 @@ export { missingReconcile } from './missing-reconcile.js';
|
|
|
133
165
|
export { onReferencesExist } from './on-references-exist.js';
|
|
134
166
|
export { noDevPermitInSource } from './no-dev-permit-in-source.js';
|
|
135
167
|
export { noDestructuredCompose } from './no-destructured-compose.js';
|
|
168
|
+
export { noLegacyCliAliasExport } from './no-legacy-cli-alias-export.js';
|
|
136
169
|
export { noLegacyLayerImports } from './no-legacy-layer-imports.js';
|
|
137
170
|
export { noDirectImplementationCall } from './no-direct-implementation-call.js';
|
|
138
171
|
export { noNativeErrorResult } from './no-native-error-result.js';
|
|
@@ -159,8 +192,10 @@ export { resourceMockCoverage } from './resource-mock-coverage.js';
|
|
|
159
192
|
export { scheduledDestroyIntent } from './scheduled-destroy-intent.js';
|
|
160
193
|
export { signalGraphCoaching } from './signal-graph-coaching.js';
|
|
161
194
|
export { staticResourceAccessorPreference } from './static-resource-accessor-preference.js';
|
|
162
|
-
export {
|
|
195
|
+
export { surfaceOverlayCoherence } from './surface-overlay-coherence.js';
|
|
196
|
+
export { surfaceTrailheadCoherence } from './surface-trailhead-coherence.js';
|
|
163
197
|
export { trailForkCoaching } from './trail-fork-coaching.js';
|
|
198
|
+
export { trailheadOverrideDivergence } from './trailhead-override-divergence.js';
|
|
164
199
|
export {
|
|
165
200
|
forkWithoutPreservedBlaze,
|
|
166
201
|
markerSchemaUnsupported,
|
|
@@ -192,9 +227,11 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
192
227
|
[deadPublicTrail.name, deadPublicTrail],
|
|
193
228
|
[draftFileMarking.name, draftFileMarking],
|
|
194
229
|
[draftVisibleDebt.name, draftVisibleDebt],
|
|
230
|
+
[duplicateExportedSymbol.name, duplicateExportedSymbol],
|
|
195
231
|
[errorMappingCompleteness.name, errorMappingCompleteness],
|
|
196
232
|
[exampleValid.name, exampleValid],
|
|
197
233
|
[firesDeclarations.name, firesDeclarations],
|
|
234
|
+
[governedSymbolResidue.name, governedSymbolResidue],
|
|
198
235
|
[incompleteCrud.name, incompleteCrud],
|
|
199
236
|
[intentPropagation.name, intentPropagation],
|
|
200
237
|
[layerFieldNameDrift.name, layerFieldNameDrift],
|
|
@@ -214,12 +251,14 @@ export const wardenRules: ReadonlyMap<string, WardenRule> = new Map<
|
|
|
214
251
|
[resourceMockCoverage.name, resourceMockCoverage],
|
|
215
252
|
[preferSchemaInference.name, preferSchemaInference],
|
|
216
253
|
[staticResourceAccessorPreference.name, staticResourceAccessorPreference],
|
|
217
|
-
[
|
|
254
|
+
[surfaceTrailheadCoherence.name, surfaceTrailheadCoherence],
|
|
218
255
|
[trailForkCoaching.name, trailForkCoaching],
|
|
256
|
+
[trailheadOverrideDivergence.name, trailheadOverrideDivergence],
|
|
219
257
|
[validDescribeRefs.name, validDescribeRefs],
|
|
220
258
|
[noDevPermitInSource.name, noDevPermitInSource],
|
|
221
259
|
[noDestructuredCompose.name, noDestructuredCompose],
|
|
222
260
|
[noDirectImplementationCall.name, noDirectImplementationCall],
|
|
261
|
+
[noLegacyCliAliasExport.name, noLegacyCliAliasExport],
|
|
223
262
|
[noLegacyLayerImports.name, noLegacyLayerImports],
|
|
224
263
|
[noNativeErrorResult.name, noNativeErrorResult],
|
|
225
264
|
[noRedundantResultErrorWrap.name, noRedundantResultErrorWrap],
|
|
@@ -261,6 +300,7 @@ export const wardenTopoRules: ReadonlyMap<string, TopoAwareWardenRule> =
|
|
|
261
300
|
[publicUnionOutputDiscriminants.name, publicUnionOutputDiscriminants],
|
|
262
301
|
[scheduledDestroyIntent.name, scheduledDestroyIntent],
|
|
263
302
|
[signalGraphCoaching.name, signalGraphCoaching],
|
|
303
|
+
[surfaceOverlayCoherence.name, surfaceOverlayCoherence],
|
|
264
304
|
[unmaterializedActivationSource.name, unmaterializedActivationSource],
|
|
265
305
|
[validDetourContract.name, validDetourContract],
|
|
266
306
|
[deprecationWithoutGuidance.name, deprecationWithoutGuidance],
|
package/src/rules/metadata.ts
CHANGED
|
@@ -44,6 +44,7 @@ export const wardenRuleLifecycleStates = [
|
|
|
44
44
|
] as const satisfies readonly WardenRuleLifecycleState[];
|
|
45
45
|
|
|
46
46
|
export const wardenFixClasses = [
|
|
47
|
+
'export-restructure',
|
|
47
48
|
'term-rewrite',
|
|
48
49
|
] as const satisfies readonly WardenFixClass[];
|
|
49
50
|
|
|
@@ -76,10 +77,12 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
|
76
77
|
'deprecation-without-guidance': 'lifecycle',
|
|
77
78
|
'draft-file-marking': 'lifecycle',
|
|
78
79
|
'draft-visible-debt': 'lifecycle',
|
|
80
|
+
'duplicate-exported-symbol': 'general',
|
|
79
81
|
'duplicate-public-contract': 'meta',
|
|
80
82
|
'error-mapping-completeness': 'results',
|
|
81
83
|
'fires-declarations': 'signals',
|
|
82
84
|
'fork-without-preserved-blaze': 'lifecycle',
|
|
85
|
+
'governed-symbol-residue': 'lifecycle',
|
|
83
86
|
'implementation-returns-result': 'results',
|
|
84
87
|
'intent-propagation': 'composition',
|
|
85
88
|
'library-projection-coherence': 'meta',
|
|
@@ -89,6 +92,7 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
|
89
92
|
'no-destructured-compose': 'composition',
|
|
90
93
|
'no-dev-permit-in-source': 'permits',
|
|
91
94
|
'no-direct-implementation-call': 'composition',
|
|
95
|
+
'no-legacy-cli-alias-export': 'general',
|
|
92
96
|
'no-native-error-result': 'results',
|
|
93
97
|
'no-redundant-result-error-wrap': 'results',
|
|
94
98
|
'no-retired-cross-vocabulary': 'composition',
|
|
@@ -109,8 +113,10 @@ const concernByRuleName: Partial<Record<string, WardenRuleConcern>> = {
|
|
|
109
113
|
'scheduled-destroy-intent': 'lifecycle',
|
|
110
114
|
'signal-graph-coaching': 'signals',
|
|
111
115
|
'static-resource-accessor-preference': 'resources',
|
|
112
|
-
'surface-
|
|
116
|
+
'surface-overlay-coherence': 'meta',
|
|
117
|
+
'surface-trailhead-coherence': 'meta',
|
|
113
118
|
'trail-fork-coaching': 'meta',
|
|
119
|
+
'trailhead-override-divergence': 'meta',
|
|
114
120
|
'unmaterialized-activation-source': 'lifecycle',
|
|
115
121
|
'valid-detour-contract': 'results',
|
|
116
122
|
'version-gap': 'lifecycle',
|
|
@@ -220,6 +226,26 @@ const builtinWardenRuleMetadataInput = {
|
|
|
220
226
|
invariant: 'Draft-authored IDs remain visible debt.',
|
|
221
227
|
tier: 'source-static',
|
|
222
228
|
},
|
|
229
|
+
'duplicate-exported-symbol': {
|
|
230
|
+
...durableRepoLocal,
|
|
231
|
+
guidance: {
|
|
232
|
+
docs: [
|
|
233
|
+
{
|
|
234
|
+
label: 'Package ownership',
|
|
235
|
+
path: 'docs/contributing/package-ownership.md',
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
relatedRules: [
|
|
239
|
+
'resolved-import-boundary',
|
|
240
|
+
'public-internal-deep-imports',
|
|
241
|
+
],
|
|
242
|
+
summary:
|
|
243
|
+
'Keep exported symbol ownership from drifting across first-party packages.',
|
|
244
|
+
},
|
|
245
|
+
invariant:
|
|
246
|
+
'First-party packages should not define the same exported symbol name in parallel.',
|
|
247
|
+
tier: 'project-static',
|
|
248
|
+
},
|
|
223
249
|
'duplicate-public-contract': {
|
|
224
250
|
...durableExternal,
|
|
225
251
|
guidance: {
|
|
@@ -265,6 +291,13 @@ const builtinWardenRuleMetadataInput = {
|
|
|
265
291
|
invariant: 'Fork version entries preserve their historical blaze.',
|
|
266
292
|
tier: 'source-static',
|
|
267
293
|
},
|
|
294
|
+
'governed-symbol-residue': {
|
|
295
|
+
...durableExternal,
|
|
296
|
+
fix: { class: 'term-rewrite', safety: 'safe' },
|
|
297
|
+
invariant:
|
|
298
|
+
'Active governed vocabulary symbol renames do not leave retired identifiers in source.',
|
|
299
|
+
tier: 'source-static',
|
|
300
|
+
},
|
|
268
301
|
'implementation-returns-result': {
|
|
269
302
|
...durableExternal,
|
|
270
303
|
invariant: 'Blazes return Result values.',
|
|
@@ -300,7 +333,10 @@ const builtinWardenRuleMetadataInput = {
|
|
|
300
333
|
path: 'docs/adr/drafts/20260612-library-surface-and-compiler.md',
|
|
301
334
|
},
|
|
302
335
|
],
|
|
303
|
-
relatedRules: [
|
|
336
|
+
relatedRules: [
|
|
337
|
+
'cli-command-route-coherence',
|
|
338
|
+
'surface-trailhead-coherence',
|
|
339
|
+
],
|
|
304
340
|
steps: [
|
|
305
341
|
'Rename one source trail or add an explicit library export override before generating a package.',
|
|
306
342
|
'Keep serialized library projection exports attached to existing trail IDs.',
|
|
@@ -346,6 +382,18 @@ const builtinWardenRuleMetadataInput = {
|
|
|
346
382
|
invariant: 'Application code composes trails through ctx.compose().',
|
|
347
383
|
tier: 'source-static',
|
|
348
384
|
},
|
|
385
|
+
'no-legacy-cli-alias-export': {
|
|
386
|
+
fix: { class: 'export-restructure', safety: 'review' },
|
|
387
|
+
invariant:
|
|
388
|
+
'Legacy app-module CLI alias exports (cliAliases, trailsCliAliases) removed in the TRL-1207 surfaces-overlay cutover do not reappear in committed source.',
|
|
389
|
+
lifecycle: {
|
|
390
|
+
retireWhen:
|
|
391
|
+
'TRL-1207 surfaces-overlay migration window closes and supported apps have moved CLI bindings into surfaceOverlay entries in their trailsOverlays exports.',
|
|
392
|
+
state: 'temporary',
|
|
393
|
+
},
|
|
394
|
+
scope: 'external',
|
|
395
|
+
tier: 'source-static',
|
|
396
|
+
},
|
|
349
397
|
'no-legacy-layer-imports': {
|
|
350
398
|
fix: { class: 'term-rewrite', safety: 'review' },
|
|
351
399
|
invariant:
|
|
@@ -605,19 +653,38 @@ const builtinWardenRuleMetadataInput = {
|
|
|
605
653
|
scope: 'advisory',
|
|
606
654
|
tier: 'source-static',
|
|
607
655
|
},
|
|
608
|
-
'surface-
|
|
656
|
+
'surface-overlay-coherence': {
|
|
657
|
+
...durableExternal,
|
|
658
|
+
guidance: {
|
|
659
|
+
relatedRules: [
|
|
660
|
+
'cli-command-route-coherence',
|
|
661
|
+
'surface-trailhead-coherence',
|
|
662
|
+
],
|
|
663
|
+
steps: [
|
|
664
|
+
'Point every binding selector at an existing trail id or dotted trail-id glob.',
|
|
665
|
+
'Narrow overlapping grouped bindings so each trail has one grouped owner per surface.',
|
|
666
|
+
'Rename bindings that shadow canonical CLI command paths or derived MCP tool names.',
|
|
667
|
+
],
|
|
668
|
+
summary:
|
|
669
|
+
'Keep surface overlay bindings pointed at real trails without shadowing canonical surface entries.',
|
|
670
|
+
},
|
|
671
|
+
invariant:
|
|
672
|
+
'Surface overlay bindings resolve to real trails without group overlap or canonical-entry shadowing.',
|
|
673
|
+
tier: 'topo-aware',
|
|
674
|
+
},
|
|
675
|
+
'surface-trailhead-coherence': {
|
|
609
676
|
...durableExternal,
|
|
610
677
|
guidance: {
|
|
611
678
|
docs: [
|
|
612
679
|
{
|
|
613
680
|
label: 'Trailheads ADR',
|
|
614
|
-
path: 'docs/adr/drafts/20260603-surface-
|
|
681
|
+
path: 'docs/adr/drafts/20260603-surface-trailheads-shape-dense-topos.md',
|
|
615
682
|
},
|
|
616
683
|
],
|
|
617
684
|
steps: [
|
|
618
|
-
'Keep
|
|
619
|
-
'Ensure each public trail belongs to one
|
|
620
|
-
'Record explicit visibility-widening acceptance and stable-description metadata when a
|
|
685
|
+
'Keep trailhead selectors as explicit string literals or literal arrays when possible.',
|
|
686
|
+
'Ensure each public trail belongs to one trailhead owner.',
|
|
687
|
+
'Record explicit visibility-widening acceptance and stable-description metadata when a trailhead intentionally widens visibility.',
|
|
621
688
|
],
|
|
622
689
|
summary:
|
|
623
690
|
'Keep trailhead maps reviewable before they reach MCP projection.',
|
|
@@ -639,12 +706,15 @@ const builtinWardenRuleMetadataInput = {
|
|
|
639
706
|
path: 'docs/surfaces/surface-accommodations.md',
|
|
640
707
|
},
|
|
641
708
|
],
|
|
642
|
-
relatedRules: [
|
|
709
|
+
relatedRules: [
|
|
710
|
+
'surface-trailhead-coherence',
|
|
711
|
+
'cli-command-route-coherence',
|
|
712
|
+
],
|
|
643
713
|
steps: [
|
|
644
714
|
'Check the semantic fork boundary: intent, permits, outputs, errors, lifecycle, and side effects.',
|
|
645
715
|
'Check the structural fork boundary: selected trail identity stays visible instead of hiding behind action vocabulary.',
|
|
646
716
|
'Split real capability forks into distinct trails or a composing trail.',
|
|
647
|
-
'Use a
|
|
717
|
+
'Use a trailhead only when one surface entry needs to group multiple trails while preserving selected member identity.',
|
|
648
718
|
],
|
|
649
719
|
summary:
|
|
650
720
|
'Keep surface accommodations from hiding several capabilities behind one branching trail input.',
|
|
@@ -654,6 +724,30 @@ const builtinWardenRuleMetadataInput = {
|
|
|
654
724
|
scope: 'advisory',
|
|
655
725
|
tier: 'source-static',
|
|
656
726
|
},
|
|
727
|
+
'trailhead-override-divergence': {
|
|
728
|
+
...durableExternal,
|
|
729
|
+
guidance: {
|
|
730
|
+
docs: [
|
|
731
|
+
{
|
|
732
|
+
label: 'Surface Accommodations',
|
|
733
|
+
path: 'docs/surfaces/surface-accommodations.md',
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
relatedRules: [
|
|
737
|
+
'surface-overlay-coherence',
|
|
738
|
+
'surface-trailhead-coherence',
|
|
739
|
+
],
|
|
740
|
+
steps: [
|
|
741
|
+
'Author the grouped entry as an mcp list binding in surfaceOverlay({ mcp }) so the lock carries the default.',
|
|
742
|
+
'Keep the call-site trailhead map aligned with the authored binding names and member selectors, or rename one side to make the intentional divergence explicit.',
|
|
743
|
+
],
|
|
744
|
+
summary:
|
|
745
|
+
'Keep call-site MCP trailhead overrides aligned with the authored overlay default.',
|
|
746
|
+
},
|
|
747
|
+
invariant:
|
|
748
|
+
'Call-site MCP trailhead maps stay aligned with authored surfaces overlay mcp bindings.',
|
|
749
|
+
tier: 'project-static',
|
|
750
|
+
},
|
|
657
751
|
'unmaterialized-activation-source': {
|
|
658
752
|
...durableExternal,
|
|
659
753
|
invariant:
|
|
@@ -715,7 +809,7 @@ const builtinWardenRuleMetadataInput = {
|
|
|
715
809
|
|
|
716
810
|
export type BuiltinWardenRuleName = keyof typeof builtinWardenRuleMetadataInput;
|
|
717
811
|
|
|
718
|
-
const
|
|
812
|
+
const withRuleDefaults = (
|
|
719
813
|
name: string,
|
|
720
814
|
metadata: BuiltinWardenRuleMetadataInput
|
|
721
815
|
): WardenRuleMetadata => ({
|
|
@@ -727,7 +821,7 @@ const withFacetDefaults = (
|
|
|
727
821
|
export const builtinWardenRuleMetadata = Object.fromEntries(
|
|
728
822
|
Object.entries(builtinWardenRuleMetadataInput).map(([name, metadata]) => [
|
|
729
823
|
name,
|
|
730
|
-
|
|
824
|
+
withRuleDefaults(name, metadata),
|
|
731
825
|
])
|
|
732
826
|
) as Readonly<Record<BuiltinWardenRuleName, WardenRuleMetadata>>;
|
|
733
827
|
|