@webpieces/code-rules 0.4.691 → 0.4.693

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/code-rules",
3
- "version": "0.4.691",
3
+ "version": "0.4.693",
4
4
  "description": "Standalone code validation rules extracted from architecture-validators, no Nx dependency required",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -16,7 +16,7 @@
16
16
  "directory": "packages/tooling/code-rules"
17
17
  },
18
18
  "dependencies": {
19
- "@webpieces/rules-config": "0.4.691",
19
+ "@webpieces/rules-config": "0.4.693",
20
20
  "@inversifyjs/binding-decorators": "1.1.5",
21
21
  "inversify": "7.10.4",
22
22
  "reflect-metadata": "0.2.2"
@@ -24,6 +24,7 @@ exports.CONFIG_BINDINGS = [
24
24
  [rules_config_1.NoSymbolDiTokensConfig, 'no-symbol-di-tokens'],
25
25
  [rules_config_1.NoClientCreationOutsideServerOrClientConfig, 'no-client-creation-outside-server-or-client'],
26
26
  [rules_config_1.NoCustomCssConfig, 'no-custom-css'],
27
+ [rules_config_1.NoStatePathsInTemplatesConfig, 'no-state-paths-in-templates'],
27
28
  [rules_config_1.NoProcessExitOutsideMainConfig, 'no-process-exit-outside-main'],
28
29
  [rules_config_1.NoFunctionOutsideClassConfig, 'no-function-outside-class'],
29
30
  [rules_config_1.InjectAnnotationNotNeededForConcreteClassConfig, 'inject-annotation-not-needed-for-concrete-class'],
@@ -1 +1 @@
1
- {"version":3,"file":"code-rules-config-table.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-config-table.ts"],"names":[],"mappings":";;;AAAA,0DAuBiC;AAKjC;;;;;GAKG;AACU,QAAA,eAAe,GAAqE;IAC7F,CAAC,mCAAoB,EAAE,kBAAkB,CAAC;IAC1C,CAAC,iCAAkB,EAAE,gBAAgB,CAAC;IACtC,CAAC,sCAAuB,EAAE,qBAAqB,CAAC;IAChD,CAAC,yCAA0B,EAAE,yBAAyB,CAAC;IACvD,CAAC,iCAAkB,EAAE,gBAAgB,CAAC;IACtC,CAAC,kCAAmB,EAAE,iBAAiB,CAAC;IACxC,CAAC,uCAAwB,EAAE,sBAAsB,CAAC;IAClD,CAAC,oCAAqB,EAAE,kBAAkB,CAAC;IAC3C,CAAC,kCAAmB,EAAE,gBAAgB,CAAC;IACvC,CAAC,sCAAuB,EAAE,qBAAqB,CAAC;IAChD,CAAC,0CAA2B,EAAE,yBAAyB,CAAC;IACxD,CAAC,iDAAkC,EAAE,mCAAmC,CAAC;IACzE,CAAC,qCAAsB,EAAE,qBAAqB,CAAC;IAC/C,CAAC,0DAA2C,EAAE,6CAA6C,CAAC;IAC5F,CAAC,gCAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,6CAA8B,EAAE,8BAA8B,CAAC;IAChE,CAAC,2CAA4B,EAAE,2BAA2B,CAAC;IAC3D,CAAC,8DAA+C,EAAE,iDAAiD,CAAC;IACpG,CAAC,iCAAkB,EAAE,eAAe,CAAC;IACrC,CAAC,4BAAa,EAAE,UAAU,CAAC;CAC9B,CAAC","sourcesContent":["import {\n BaseRuleConfig,\n WebpiecesRulesConfig,\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n CatchErrorPatternConfig,\n NoUnmanagedExceptionsConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoClientCreationOutsideServerOrClientConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n} from '@webpieces/rules-config';\n\n/** A rule config class usable as an inversify inject-by-type token. */\nexport type ConfigCtor = new () => BaseRuleConfig;\n\n/**\n * The 1:1 table of `[config class, webpieces.config.json rule key]`. The composition root iterates it\n * to bind each rule's config into the container (`bind(ConfigClass).toConstantValue(config[key] ??\n * new ConfigClass())`), so every `@provideSingleton` validator injects its config by type. Data only\n * — the binding loop lives inline in the bin (an inline loop, exempt from no-function-outside-class).\n */\nexport const CONFIG_BINDINGS: ReadonlyArray<readonly [ConfigCtor, keyof WebpiecesRulesConfig]> = [\n [MaxMethodLinesConfig, 'max-method-lines'],\n [MaxFileLinesConfig, 'max-file-lines'],\n [RequireReturnTypeConfig, 'require-return-type'],\n [NoInlineTypeLiteralsConfig, 'no-inline-type-literals'],\n [NoAnyUnknownConfig, 'no-any-unknown'],\n [NoImplicitAnyConfig, 'no-implicit-any'],\n [PrismaValidateDtosConfig, 'prisma-validate-dtos'],\n [PrismaConverterConfig, 'prisma-converter'],\n [NoDestructureConfig, 'no-destructure'],\n [CatchErrorPatternConfig, 'catch-error-pattern'],\n [NoUnmanagedExceptionsConfig, 'no-unmanaged-exceptions'],\n [AngularNoDirectApiInResolverConfig, 'angular-no-direct-api-in-resolver'],\n [NoSymbolDiTokensConfig, 'no-symbol-di-tokens'],\n [NoClientCreationOutsideServerOrClientConfig, 'no-client-creation-outside-server-or-client'],\n [NoCustomCssConfig, 'no-custom-css'],\n [NoProcessExitOutsideMainConfig, 'no-process-exit-outside-main'],\n [NoFunctionOutsideClassConfig, 'no-function-outside-class'],\n [InjectAnnotationNotNeededForConcreteClassConfig, 'inject-annotation-not-needed-for-concrete-class'],\n [FrameworkTagConfig, 'framework-tag'],\n [RoleTagConfig, 'role-tag'],\n];\n"]}
1
+ {"version":3,"file":"code-rules-config-table.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-config-table.ts"],"names":[],"mappings":";;;AAAA,0DAwBiC;AAKjC;;;;;GAKG;AACU,QAAA,eAAe,GAAqE;IAC7F,CAAC,mCAAoB,EAAE,kBAAkB,CAAC;IAC1C,CAAC,iCAAkB,EAAE,gBAAgB,CAAC;IACtC,CAAC,sCAAuB,EAAE,qBAAqB,CAAC;IAChD,CAAC,yCAA0B,EAAE,yBAAyB,CAAC;IACvD,CAAC,iCAAkB,EAAE,gBAAgB,CAAC;IACtC,CAAC,kCAAmB,EAAE,iBAAiB,CAAC;IACxC,CAAC,uCAAwB,EAAE,sBAAsB,CAAC;IAClD,CAAC,oCAAqB,EAAE,kBAAkB,CAAC;IAC3C,CAAC,kCAAmB,EAAE,gBAAgB,CAAC;IACvC,CAAC,sCAAuB,EAAE,qBAAqB,CAAC;IAChD,CAAC,0CAA2B,EAAE,yBAAyB,CAAC;IACxD,CAAC,iDAAkC,EAAE,mCAAmC,CAAC;IACzE,CAAC,qCAAsB,EAAE,qBAAqB,CAAC;IAC/C,CAAC,0DAA2C,EAAE,6CAA6C,CAAC;IAC5F,CAAC,gCAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,4CAA6B,EAAE,6BAA6B,CAAC;IAC9D,CAAC,6CAA8B,EAAE,8BAA8B,CAAC;IAChE,CAAC,2CAA4B,EAAE,2BAA2B,CAAC;IAC3D,CAAC,8DAA+C,EAAE,iDAAiD,CAAC;IACpG,CAAC,iCAAkB,EAAE,eAAe,CAAC;IACrC,CAAC,4BAAa,EAAE,UAAU,CAAC;CAC9B,CAAC","sourcesContent":["import {\n BaseRuleConfig,\n WebpiecesRulesConfig,\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n CatchErrorPatternConfig,\n NoUnmanagedExceptionsConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoClientCreationOutsideServerOrClientConfig,\n NoCustomCssConfig,\n NoStatePathsInTemplatesConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n} from '@webpieces/rules-config';\n\n/** A rule config class usable as an inversify inject-by-type token. */\nexport type ConfigCtor = new () => BaseRuleConfig;\n\n/**\n * The 1:1 table of `[config class, webpieces.config.json rule key]`. The composition root iterates it\n * to bind each rule's config into the container (`bind(ConfigClass).toConstantValue(config[key] ??\n * new ConfigClass())`), so every `@provideSingleton` validator injects its config by type. Data only\n * — the binding loop lives inline in the bin (an inline loop, exempt from no-function-outside-class).\n */\nexport const CONFIG_BINDINGS: ReadonlyArray<readonly [ConfigCtor, keyof WebpiecesRulesConfig]> = [\n [MaxMethodLinesConfig, 'max-method-lines'],\n [MaxFileLinesConfig, 'max-file-lines'],\n [RequireReturnTypeConfig, 'require-return-type'],\n [NoInlineTypeLiteralsConfig, 'no-inline-type-literals'],\n [NoAnyUnknownConfig, 'no-any-unknown'],\n [NoImplicitAnyConfig, 'no-implicit-any'],\n [PrismaValidateDtosConfig, 'prisma-validate-dtos'],\n [PrismaConverterConfig, 'prisma-converter'],\n [NoDestructureConfig, 'no-destructure'],\n [CatchErrorPatternConfig, 'catch-error-pattern'],\n [NoUnmanagedExceptionsConfig, 'no-unmanaged-exceptions'],\n [AngularNoDirectApiInResolverConfig, 'angular-no-direct-api-in-resolver'],\n [NoSymbolDiTokensConfig, 'no-symbol-di-tokens'],\n [NoClientCreationOutsideServerOrClientConfig, 'no-client-creation-outside-server-or-client'],\n [NoCustomCssConfig, 'no-custom-css'],\n [NoStatePathsInTemplatesConfig, 'no-state-paths-in-templates'],\n [NoProcessExitOutsideMainConfig, 'no-process-exit-outside-main'],\n [NoFunctionOutsideClassConfig, 'no-function-outside-class'],\n [InjectAnnotationNotNeededForConcreteClassConfig, 'inject-annotation-not-needed-for-concrete-class'],\n [FrameworkTagConfig, 'framework-tag'],\n [RoleTagConfig, 'role-tag'],\n];\n"]}
@@ -17,6 +17,7 @@ import { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver'
17
17
  import { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';
18
18
  import { NoClientCreationOutsideServerOrClientValidator } from './validate-no-client-creation-outside-server-or-client';
19
19
  import { NoCustomCssValidator } from './validate-no-custom-css';
20
+ import { NoStatePathsInTemplatesValidator } from './validate-no-state-paths-in-templates';
20
21
  import { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';
21
22
  import { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';
22
23
  import { InjectAnnotationNotNeededForConcreteClassValidator } from './validate-inject-annotation-not-needed-for-concrete-class';
@@ -48,13 +49,14 @@ export declare class CodeRulesEngine {
48
49
  private readonly noSymbolDiTokens;
49
50
  private readonly noClientCreationOutsideServerOrClient;
50
51
  private readonly noCustomCss;
52
+ private readonly noStatePathsInTemplates;
51
53
  private readonly noProcessExitOutsideMain;
52
54
  private readonly noFunctionOutsideClass;
53
55
  private readonly injectAnnotationNotNeeded;
54
56
  private readonly frameworkTag;
55
57
  private readonly roleTag;
56
- constructor(workspace: WorkspaceRoot, reporter: RuleReporter, matchRules: MatchRulesHolder, matchChecker: MatchRulesChecker, maxMethodLines: MaxMethodLinesValidator, maxFileLines: MaxFileLinesValidator, requireReturnType: RequireReturnTypeValidator, noInlineTypeLiterals: NoInlineTypeLiteralsValidator, noAnyUnknown: NoAnyUnknownValidator, noImplicitAny: NoImplicitAnyValidator, prismaValidateDtos: PrismaValidateDtosValidator, prismaConverter: PrismaConverterValidator, noDestructure: NoDestructureValidator, catchErrorPattern: CatchErrorPatternValidator, noUnmanagedExceptions: NoUnmanagedExceptionsValidator, noDirectApiResolver: NoDirectApiResolverValidator, noSymbolDiTokens: NoSymbolDiTokensValidator, noClientCreationOutsideServerOrClient: NoClientCreationOutsideServerOrClientValidator, noCustomCss: NoCustomCssValidator, noProcessExitOutsideMain: NoProcessExitOutsideMainValidator, noFunctionOutsideClass: NoFunctionOutsideClassValidator, injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator, frameworkTag: FrameworkTagValidator, roleTag: RoleTagValidator);
57
- /** The 20 injected built-in validators, in run order. */
58
+ constructor(workspace: WorkspaceRoot, reporter: RuleReporter, matchRules: MatchRulesHolder, matchChecker: MatchRulesChecker, maxMethodLines: MaxMethodLinesValidator, maxFileLines: MaxFileLinesValidator, requireReturnType: RequireReturnTypeValidator, noInlineTypeLiterals: NoInlineTypeLiteralsValidator, noAnyUnknown: NoAnyUnknownValidator, noImplicitAny: NoImplicitAnyValidator, prismaValidateDtos: PrismaValidateDtosValidator, prismaConverter: PrismaConverterValidator, noDestructure: NoDestructureValidator, catchErrorPattern: CatchErrorPatternValidator, noUnmanagedExceptions: NoUnmanagedExceptionsValidator, noDirectApiResolver: NoDirectApiResolverValidator, noSymbolDiTokens: NoSymbolDiTokensValidator, noClientCreationOutsideServerOrClient: NoClientCreationOutsideServerOrClientValidator, noCustomCss: NoCustomCssValidator, noStatePathsInTemplates: NoStatePathsInTemplatesValidator, noProcessExitOutsideMain: NoProcessExitOutsideMainValidator, noFunctionOutsideClass: NoFunctionOutsideClassValidator, injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator, frameworkTag: FrameworkTagValidator, roleTag: RoleTagValidator);
59
+ /** The 21 injected built-in validators, in run order. */
58
60
  private builtIns;
59
61
  /**
60
62
  * Every ACTIVE run: an injected built-in validator whose `shouldRun()` is true, or an injected
@@ -22,6 +22,7 @@ const validate_no_direct_api_resolver_1 = require("./validate-no-direct-api-reso
22
22
  const validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens");
23
23
  const validate_no_client_creation_outside_server_or_client_1 = require("./validate-no-client-creation-outside-server-or-client");
24
24
  const validate_no_custom_css_1 = require("./validate-no-custom-css");
25
+ const validate_no_state_paths_in_templates_1 = require("./validate-no-state-paths-in-templates");
25
26
  const validate_no_process_exit_outside_main_1 = require("./validate-no-process-exit-outside-main");
26
27
  const validate_no_function_outside_class_1 = require("./validate-no-function-outside-class");
27
28
  const validate_inject_annotation_not_needed_for_concrete_class_1 = require("./validate-inject-annotation-not-needed-for-concrete-class");
@@ -53,13 +54,14 @@ let CodeRulesEngine = class CodeRulesEngine {
53
54
  noSymbolDiTokens;
54
55
  noClientCreationOutsideServerOrClient;
55
56
  noCustomCss;
57
+ noStatePathsInTemplates;
56
58
  noProcessExitOutsideMain;
57
59
  noFunctionOutsideClass;
58
60
  injectAnnotationNotNeeded;
59
61
  frameworkTag;
60
62
  roleTag;
61
63
  // webpieces-disable max-lines-new-methods -- the built-in validator set is flat; each is one injected field
62
- constructor(workspace, reporter, matchRules, matchChecker, maxMethodLines, maxFileLines, requireReturnType, noInlineTypeLiterals, noAnyUnknown, noImplicitAny, prismaValidateDtos, prismaConverter, noDestructure, catchErrorPattern, noUnmanagedExceptions, noDirectApiResolver, noSymbolDiTokens, noClientCreationOutsideServerOrClient, noCustomCss, noProcessExitOutsideMain, noFunctionOutsideClass, injectAnnotationNotNeeded, frameworkTag, roleTag) {
64
+ constructor(workspace, reporter, matchRules, matchChecker, maxMethodLines, maxFileLines, requireReturnType, noInlineTypeLiterals, noAnyUnknown, noImplicitAny, prismaValidateDtos, prismaConverter, noDestructure, catchErrorPattern, noUnmanagedExceptions, noDirectApiResolver, noSymbolDiTokens, noClientCreationOutsideServerOrClient, noCustomCss, noStatePathsInTemplates, noProcessExitOutsideMain, noFunctionOutsideClass, injectAnnotationNotNeeded, frameworkTag, roleTag) {
63
65
  this.workspace = workspace;
64
66
  this.reporter = reporter;
65
67
  this.matchRules = matchRules;
@@ -79,19 +81,21 @@ let CodeRulesEngine = class CodeRulesEngine {
79
81
  this.noSymbolDiTokens = noSymbolDiTokens;
80
82
  this.noClientCreationOutsideServerOrClient = noClientCreationOutsideServerOrClient;
81
83
  this.noCustomCss = noCustomCss;
84
+ this.noStatePathsInTemplates = noStatePathsInTemplates;
82
85
  this.noProcessExitOutsideMain = noProcessExitOutsideMain;
83
86
  this.noFunctionOutsideClass = noFunctionOutsideClass;
84
87
  this.injectAnnotationNotNeeded = injectAnnotationNotNeeded;
85
88
  this.frameworkTag = frameworkTag;
86
89
  this.roleTag = roleTag;
87
90
  }
88
- /** The 20 injected built-in validators, in run order. */
91
+ /** The 21 injected built-in validators, in run order. */
89
92
  builtIns() {
90
93
  return [
91
94
  this.maxMethodLines, this.maxFileLines, this.requireReturnType, this.noInlineTypeLiterals,
92
95
  this.noAnyUnknown, this.noImplicitAny, this.prismaValidateDtos, this.prismaConverter,
93
96
  this.noDestructure, this.catchErrorPattern, this.noUnmanagedExceptions, this.noDirectApiResolver,
94
97
  this.noSymbolDiTokens, this.noClientCreationOutsideServerOrClient, this.noCustomCss,
98
+ this.noStatePathsInTemplates,
95
99
  this.noProcessExitOutsideMain, this.noFunctionOutsideClass,
96
100
  this.injectAnnotationNotNeeded, this.frameworkTag, this.roleTag,
97
101
  ];
@@ -154,6 +158,7 @@ exports.CodeRulesEngine = CodeRulesEngine = tslib_1.__decorate([
154
158
  validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator,
155
159
  validate_no_client_creation_outside_server_or_client_1.NoClientCreationOutsideServerOrClientValidator,
156
160
  validate_no_custom_css_1.NoCustomCssValidator,
161
+ validate_no_state_paths_in_templates_1.NoStatePathsInTemplatesValidator,
157
162
  validate_no_process_exit_outside_main_1.NoProcessExitOutsideMainValidator,
158
163
  validate_no_function_outside_class_1.NoFunctionOutsideClassValidator,
159
164
  validate_inject_annotation_not_needed_for_concrete_class_1.InjectAnnotationNotNeededForConcreteClassValidator,
@@ -1 +1 @@
1
- {"version":3,"file":"code-rules-engine.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { CodeValidator, ExecutorResult, RuleRun } from './code-validator';\nimport { RuleReporter } from './rule-reporter';\nimport { WorkspaceRoot, MatchRulesHolder } from './code-rules-context';\nimport { MatchRulesChecker } from './validate-match-rules';\nimport { MaxMethodLinesValidator } from './validate-modified-methods';\nimport { MaxFileLinesValidator } from './validate-modified-files';\nimport { RequireReturnTypeValidator } from './validate-return-types';\nimport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nimport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nimport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nimport { PrismaValidateDtosValidator } from './validate-dtos';\nimport { PrismaConverterValidator } from './validate-prisma-converters';\nimport { NoDestructureValidator } from './validate-no-destructure';\nimport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nimport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nimport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nimport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nimport { NoClientCreationOutsideServerOrClientValidator } from './validate-no-client-creation-outside-server-or-client';\nimport { NoCustomCssValidator } from './validate-no-custom-css';\nimport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nimport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nimport { InjectAnnotationNotNeededForConcreteClassValidator } from './validate-inject-annotation-not-needed-for-concrete-class';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\n\n/**\n * Owns running the code-rules suite. Every built-in validator is injected as a singleton (its config\n * is bound in the container at bootstrap), so this class IS the code-rules DI DAG the design graph\n * draws. The per-entry match-rules validators are the one exception — they are config-per-instance,\n * so they are built from the injected {@link MatchRulesHolder}.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class CodeRulesEngine {\n // webpieces-disable max-lines-new-methods -- the built-in validator set is flat; each is one injected field\n constructor(\n private readonly workspace: WorkspaceRoot,\n private readonly reporter: RuleReporter,\n private readonly matchRules: MatchRulesHolder,\n private readonly matchChecker: MatchRulesChecker,\n private readonly maxMethodLines: MaxMethodLinesValidator,\n private readonly maxFileLines: MaxFileLinesValidator,\n private readonly requireReturnType: RequireReturnTypeValidator,\n private readonly noInlineTypeLiterals: NoInlineTypeLiteralsValidator,\n private readonly noAnyUnknown: NoAnyUnknownValidator,\n private readonly noImplicitAny: NoImplicitAnyValidator,\n private readonly prismaValidateDtos: PrismaValidateDtosValidator,\n private readonly prismaConverter: PrismaConverterValidator,\n private readonly noDestructure: NoDestructureValidator,\n private readonly catchErrorPattern: CatchErrorPatternValidator,\n private readonly noUnmanagedExceptions: NoUnmanagedExceptionsValidator,\n private readonly noDirectApiResolver: NoDirectApiResolverValidator,\n private readonly noSymbolDiTokens: NoSymbolDiTokensValidator,\n private readonly noClientCreationOutsideServerOrClient: NoClientCreationOutsideServerOrClientValidator,\n private readonly noCustomCss: NoCustomCssValidator,\n private readonly noProcessExitOutsideMain: NoProcessExitOutsideMainValidator,\n private readonly noFunctionOutsideClass: NoFunctionOutsideClassValidator,\n private readonly injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator,\n private readonly frameworkTag: FrameworkTagValidator,\n private readonly roleTag: RoleTagValidator,\n ) {}\n\n /** The 20 injected built-in validators, in run order. */\n private builtIns(): CodeValidator<BaseRuleConfig>[] {\n return [\n this.maxMethodLines, this.maxFileLines, this.requireReturnType, this.noInlineTypeLiterals,\n this.noAnyUnknown, this.noImplicitAny, this.prismaValidateDtos, this.prismaConverter,\n this.noDestructure, this.catchErrorPattern, this.noUnmanagedExceptions, this.noDirectApiResolver,\n this.noSymbolDiTokens, this.noClientCreationOutsideServerOrClient, this.noCustomCss,\n this.noProcessExitOutsideMain, this.noFunctionOutsideClass,\n this.injectAnnotationNotNeeded, this.frameworkTag, this.roleTag,\n ];\n }\n\n /**\n * Every ACTIVE run: an injected built-in validator whose `shouldRun()` is true, or an injected\n * match-rule check per configured entry that is active. Built as {@link RuleRun} value objects\n * (name + thunk) so NO DAG member is `new`-ed — the validators and the checker are injected.\n */\n private activeRuns(root: string): RuleRun[] {\n const runs: RuleRun[] = [];\n for (const v of this.builtIns()) {\n if (v.shouldRun()) runs.push(new RuleRun(v.name, () => v.run(root)));\n }\n for (const mr of this.matchRules.rules) {\n if (this.matchChecker.shouldRun(mr)) runs.push(new RuleRun(mr.name, () => this.matchChecker.runForConfig(mr, root)));\n }\n return runs;\n }\n\n /**\n * Run all configured code validators against the workspace. A validator runs only when\n * `shouldRun()` is true (mode not OFF and no branch/epoch escape hatch). Per-run isolation lives\n * in {@link RuleReporter} so one validator can never abort the rest.\n */\n async run(): Promise<ExecutorResult> {\n const runs = this.activeRuns(this.workspace.path);\n if (runs.length === 0) {\n console.log('\\n⏭️ Skipping all code validations (all modes: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Active rules: ${runs.map((r: RuleRun) => r.name).join(', ')}`);\n console.log('');\n\n const result = await this.reporter.runValidators(runs);\n console.log(result.success ? '\\n✅ All code validations passed\\n' : '\\n❌ Some code validations failed\\n');\n return result;\n }\n}\n"]}
1
+ {"version":3,"file":"code-rules-engine.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/code-rules-engine.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseRuleConfig } from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { CodeValidator, ExecutorResult, RuleRun } from './code-validator';\nimport { RuleReporter } from './rule-reporter';\nimport { WorkspaceRoot, MatchRulesHolder } from './code-rules-context';\nimport { MatchRulesChecker } from './validate-match-rules';\nimport { MaxMethodLinesValidator } from './validate-modified-methods';\nimport { MaxFileLinesValidator } from './validate-modified-files';\nimport { RequireReturnTypeValidator } from './validate-return-types';\nimport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nimport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nimport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nimport { PrismaValidateDtosValidator } from './validate-dtos';\nimport { PrismaConverterValidator } from './validate-prisma-converters';\nimport { NoDestructureValidator } from './validate-no-destructure';\nimport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nimport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nimport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nimport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nimport { NoClientCreationOutsideServerOrClientValidator } from './validate-no-client-creation-outside-server-or-client';\nimport { NoCustomCssValidator } from './validate-no-custom-css';\nimport { NoStatePathsInTemplatesValidator } from './validate-no-state-paths-in-templates';\nimport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nimport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nimport { InjectAnnotationNotNeededForConcreteClassValidator } from './validate-inject-annotation-not-needed-for-concrete-class';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\n\n/**\n * Owns running the code-rules suite. Every built-in validator is injected as a singleton (its config\n * is bound in the container at bootstrap), so this class IS the code-rules DI DAG the design graph\n * draws. The per-entry match-rules validators are the one exception — they are config-per-instance,\n * so they are built from the injected {@link MatchRulesHolder}.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class CodeRulesEngine {\n // webpieces-disable max-lines-new-methods -- the built-in validator set is flat; each is one injected field\n constructor(\n private readonly workspace: WorkspaceRoot,\n private readonly reporter: RuleReporter,\n private readonly matchRules: MatchRulesHolder,\n private readonly matchChecker: MatchRulesChecker,\n private readonly maxMethodLines: MaxMethodLinesValidator,\n private readonly maxFileLines: MaxFileLinesValidator,\n private readonly requireReturnType: RequireReturnTypeValidator,\n private readonly noInlineTypeLiterals: NoInlineTypeLiteralsValidator,\n private readonly noAnyUnknown: NoAnyUnknownValidator,\n private readonly noImplicitAny: NoImplicitAnyValidator,\n private readonly prismaValidateDtos: PrismaValidateDtosValidator,\n private readonly prismaConverter: PrismaConverterValidator,\n private readonly noDestructure: NoDestructureValidator,\n private readonly catchErrorPattern: CatchErrorPatternValidator,\n private readonly noUnmanagedExceptions: NoUnmanagedExceptionsValidator,\n private readonly noDirectApiResolver: NoDirectApiResolverValidator,\n private readonly noSymbolDiTokens: NoSymbolDiTokensValidator,\n private readonly noClientCreationOutsideServerOrClient: NoClientCreationOutsideServerOrClientValidator,\n private readonly noCustomCss: NoCustomCssValidator,\n private readonly noStatePathsInTemplates: NoStatePathsInTemplatesValidator,\n private readonly noProcessExitOutsideMain: NoProcessExitOutsideMainValidator,\n private readonly noFunctionOutsideClass: NoFunctionOutsideClassValidator,\n private readonly injectAnnotationNotNeeded: InjectAnnotationNotNeededForConcreteClassValidator,\n private readonly frameworkTag: FrameworkTagValidator,\n private readonly roleTag: RoleTagValidator,\n ) {}\n\n /** The 21 injected built-in validators, in run order. */\n private builtIns(): CodeValidator<BaseRuleConfig>[] {\n return [\n this.maxMethodLines, this.maxFileLines, this.requireReturnType, this.noInlineTypeLiterals,\n this.noAnyUnknown, this.noImplicitAny, this.prismaValidateDtos, this.prismaConverter,\n this.noDestructure, this.catchErrorPattern, this.noUnmanagedExceptions, this.noDirectApiResolver,\n this.noSymbolDiTokens, this.noClientCreationOutsideServerOrClient, this.noCustomCss,\n this.noStatePathsInTemplates,\n this.noProcessExitOutsideMain, this.noFunctionOutsideClass,\n this.injectAnnotationNotNeeded, this.frameworkTag, this.roleTag,\n ];\n }\n\n /**\n * Every ACTIVE run: an injected built-in validator whose `shouldRun()` is true, or an injected\n * match-rule check per configured entry that is active. Built as {@link RuleRun} value objects\n * (name + thunk) so NO DAG member is `new`-ed — the validators and the checker are injected.\n */\n private activeRuns(root: string): RuleRun[] {\n const runs: RuleRun[] = [];\n for (const v of this.builtIns()) {\n if (v.shouldRun()) runs.push(new RuleRun(v.name, () => v.run(root)));\n }\n for (const mr of this.matchRules.rules) {\n if (this.matchChecker.shouldRun(mr)) runs.push(new RuleRun(mr.name, () => this.matchChecker.runForConfig(mr, root)));\n }\n return runs;\n }\n\n /**\n * Run all configured code validators against the workspace. A validator runs only when\n * `shouldRun()` is true (mode not OFF and no branch/epoch escape hatch). Per-run isolation lives\n * in {@link RuleReporter} so one validator can never abort the rest.\n */\n async run(): Promise<ExecutorResult> {\n const runs = this.activeRuns(this.workspace.path);\n if (runs.length === 0) {\n console.log('\\n⏭️ Skipping all code validations (all modes: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Active rules: ${runs.map((r: RuleRun) => r.name).join(', ')}`);\n console.log('');\n\n const result = await this.reporter.runValidators(runs);\n console.log(result.success ? '\\n✅ All code validations passed\\n' : '\\n❌ Some code validations failed\\n');\n return result;\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export { NoDestructureValidator } from './validate-no-destructure';
12
12
  export { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';
13
13
  export { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';
14
14
  export { NoClientCreationOutsideServerOrClientValidator } from './validate-no-client-creation-outside-server-or-client';
15
+ export { NoStatePathsInTemplatesValidator, StatePathHit, StatePathViolation } from './validate-no-state-paths-in-templates';
15
16
  export { ProjectRoleResolver } from './project-role-resolver';
16
17
  export { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';
17
18
  export { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateCode = exports.PrismaConverterValidator = exports.PrismaValidateDtosValidator = exports.RoleTagValidator = exports.FrameworkTagValidator = exports.findRedundantInjectInSource = exports.InjectAnnotationNotNeededForConcreteClassValidator = exports.NoFunctionOutsideClassValidator = exports.NoProcessExitOutsideMainValidator = exports.ProjectRoleResolver = exports.NoClientCreationOutsideServerOrClientValidator = exports.NoSymbolDiTokensValidator = exports.NoDirectApiResolverValidator = exports.NoDestructureValidator = exports.NoUnmanagedExceptionsValidator = exports.CatchErrorPatternValidator = exports.NoInlineTypeLiteralsValidator = exports.RequireReturnTypeValidator = exports.MaxFileLinesValidator = exports.runNewMethods = exports.runModifiedMethods = exports.MaxMethodLinesValidator = exports.NoImplicitAnyValidator = exports.NoAnyUnknownValidator = exports.CodeValidator = void 0;
3
+ exports.validateCode = exports.PrismaConverterValidator = exports.PrismaValidateDtosValidator = exports.RoleTagValidator = exports.FrameworkTagValidator = exports.findRedundantInjectInSource = exports.InjectAnnotationNotNeededForConcreteClassValidator = exports.NoFunctionOutsideClassValidator = exports.NoProcessExitOutsideMainValidator = exports.ProjectRoleResolver = exports.StatePathViolation = exports.StatePathHit = exports.NoStatePathsInTemplatesValidator = exports.NoClientCreationOutsideServerOrClientValidator = exports.NoSymbolDiTokensValidator = exports.NoDirectApiResolverValidator = exports.NoDestructureValidator = exports.NoUnmanagedExceptionsValidator = exports.CatchErrorPatternValidator = exports.NoInlineTypeLiteralsValidator = exports.RequireReturnTypeValidator = exports.MaxFileLinesValidator = exports.runNewMethods = exports.runModifiedMethods = exports.MaxMethodLinesValidator = exports.NoImplicitAnyValidator = exports.NoAnyUnknownValidator = exports.CodeValidator = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  var code_validator_1 = require("./code-validator");
6
6
  Object.defineProperty(exports, "CodeValidator", { enumerable: true, get: function () { return code_validator_1.CodeValidator; } });
@@ -31,6 +31,10 @@ var validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens");
31
31
  Object.defineProperty(exports, "NoSymbolDiTokensValidator", { enumerable: true, get: function () { return validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator; } });
32
32
  var validate_no_client_creation_outside_server_or_client_1 = require("./validate-no-client-creation-outside-server-or-client");
33
33
  Object.defineProperty(exports, "NoClientCreationOutsideServerOrClientValidator", { enumerable: true, get: function () { return validate_no_client_creation_outside_server_or_client_1.NoClientCreationOutsideServerOrClientValidator; } });
34
+ var validate_no_state_paths_in_templates_1 = require("./validate-no-state-paths-in-templates");
35
+ Object.defineProperty(exports, "NoStatePathsInTemplatesValidator", { enumerable: true, get: function () { return validate_no_state_paths_in_templates_1.NoStatePathsInTemplatesValidator; } });
36
+ Object.defineProperty(exports, "StatePathHit", { enumerable: true, get: function () { return validate_no_state_paths_in_templates_1.StatePathHit; } });
37
+ Object.defineProperty(exports, "StatePathViolation", { enumerable: true, get: function () { return validate_no_state_paths_in_templates_1.StatePathViolation; } });
34
38
  var project_role_resolver_1 = require("./project-role-resolver");
35
39
  Object.defineProperty(exports, "ProjectRoleResolver", { enumerable: true, get: function () { return project_role_resolver_1.ProjectRoleResolver; } });
36
40
  var validate_no_process_exit_outside_main_1 = require("./validate-no-process-exit-outside-main");
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,mDAAiE;AAAxD,+GAAA,aAAa,OAAA;AAEtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,yEAA0F;AAAjF,oIAAA,uBAAuB,OAAA;AAAE,+HAAA,kBAAkB,OAAA;AACpD,+DAAuD;AAA9C,qHAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,iEAAqE;AAA5D,mIAAA,0BAA0B,OAAA;AACnC,uEAA2E;AAAlE,yIAAA,6BAA6B,OAAA;AACtC,+EAA4E;AAAnE,0IAAA,0BAA0B,OAAA;AACnC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,qEAAmE;AAA1D,iIAAA,sBAAsB,OAAA;AAC/B,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA;AACrC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,+HAAwH;AAA/G,sLAAA,8CAA8C,OAAA;AACvD,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA;AAC5B,iGAA4F;AAAnF,0JAAA,iCAAiC,OAAA;AAC1C,2FAAuF;AAA9E,qJAAA,+BAA+B,OAAA;AACxC,uIAA6J;AAApJ,8LAAA,kDAAkD,OAAA;AAAE,uKAAA,2BAA2B,OAAA;AACxF,mEAAiE;AAAxD,+HAAA,qBAAqB,OAAA;AAC9B,yDAAuD;AAA9C,qHAAA,gBAAgB,OAAA;AACzB,iDAA8D;AAArD,4HAAA,2BAA2B,OAAA;AACpC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,sIAAA,OAAO,OAAgB","sourcesContent":["export { CodeValidator, ExecutorResult } from './code-validator';\n\nexport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nexport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nexport { MaxMethodLinesValidator, runModifiedMethods } from './validate-modified-methods';\nexport { runNewMethods } from './validate-new-methods';\nexport { MaxFileLinesValidator } from './validate-modified-files';\nexport { RequireReturnTypeValidator } from './validate-return-types';\nexport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nexport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nexport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nexport { NoDestructureValidator } from './validate-no-destructure';\nexport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nexport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nexport { NoClientCreationOutsideServerOrClientValidator } from './validate-no-client-creation-outside-server-or-client';\nexport { ProjectRoleResolver } from './project-role-resolver';\nexport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nexport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nexport { InjectAnnotationNotNeededForConcreteClassValidator, findRedundantInjectInSource } from './validate-inject-annotation-not-needed-for-concrete-class';\nexport { FrameworkTagValidator } from './validate-framework-tag';\nexport { RoleTagValidator } from './validate-role-tag';\nexport { PrismaValidateDtosValidator } from './validate-dtos';\nexport { PrismaConverterValidator } from './validate-prisma-converters';\nexport { default as validateCode } from './validate-code';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,mDAAiE;AAAxD,+GAAA,aAAa,OAAA;AAEtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,yEAA0F;AAAjF,oIAAA,uBAAuB,OAAA;AAAE,+HAAA,kBAAkB,OAAA;AACpD,+DAAuD;AAA9C,qHAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,iEAAqE;AAA5D,mIAAA,0BAA0B,OAAA;AACnC,uEAA2E;AAAlE,yIAAA,6BAA6B,OAAA;AACtC,+EAA4E;AAAnE,0IAAA,0BAA0B,OAAA;AACnC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,qEAAmE;AAA1D,iIAAA,sBAAsB,OAAA;AAC/B,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA;AACrC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,+HAAwH;AAA/G,sLAAA,8CAA8C,OAAA;AACvD,+FAA4H;AAAnH,wJAAA,gCAAgC,OAAA;AAAE,oIAAA,YAAY,OAAA;AAAE,0IAAA,kBAAkB,OAAA;AAC3E,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA;AAC5B,iGAA4F;AAAnF,0JAAA,iCAAiC,OAAA;AAC1C,2FAAuF;AAA9E,qJAAA,+BAA+B,OAAA;AACxC,uIAA6J;AAApJ,8LAAA,kDAAkD,OAAA;AAAE,uKAAA,2BAA2B,OAAA;AACxF,mEAAiE;AAAxD,+HAAA,qBAAqB,OAAA;AAC9B,yDAAuD;AAA9C,qHAAA,gBAAgB,OAAA;AACzB,iDAA8D;AAArD,4HAAA,2BAA2B,OAAA;AACpC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,sIAAA,OAAO,OAAgB","sourcesContent":["export { CodeValidator, ExecutorResult } from './code-validator';\n\nexport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nexport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nexport { MaxMethodLinesValidator, runModifiedMethods } from './validate-modified-methods';\nexport { runNewMethods } from './validate-new-methods';\nexport { MaxFileLinesValidator } from './validate-modified-files';\nexport { RequireReturnTypeValidator } from './validate-return-types';\nexport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nexport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nexport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nexport { NoDestructureValidator } from './validate-no-destructure';\nexport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nexport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nexport { NoClientCreationOutsideServerOrClientValidator } from './validate-no-client-creation-outside-server-or-client';\nexport { NoStatePathsInTemplatesValidator, StatePathHit, StatePathViolation } from './validate-no-state-paths-in-templates';\nexport { ProjectRoleResolver } from './project-role-resolver';\nexport { NoProcessExitOutsideMainValidator } from './validate-no-process-exit-outside-main';\nexport { NoFunctionOutsideClassValidator } from './validate-no-function-outside-class';\nexport { InjectAnnotationNotNeededForConcreteClassValidator, findRedundantInjectInSource } from './validate-inject-annotation-not-needed-for-concrete-class';\nexport { FrameworkTagValidator } from './validate-framework-tag';\nexport { RoleTagValidator } from './validate-role-tag';\nexport { PrismaValidateDtosValidator } from './validate-dtos';\nexport { PrismaConverterValidator } from './validate-prisma-converters';\nexport { default as validateCode } from './validate-code';\n"]}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Validate No State Paths In Templates
3
+ *
4
+ * FAILS when a GENERATED-doc template restates a webpieces state path (`.webpieces/…`) as a literal
5
+ * instead of rendering it from the resolver that already computes it.
6
+ *
7
+ * ## The incident this exists for
8
+ *
9
+ * `webpieces.git-workflow.md` — an AI-instruction doc regenerated into `.webpieces/instruct-ai/` of
10
+ * every governed repo on every `wp-*` command — carried the literal
11
+ * `.webpieces/logs/branch-mutations.log`. That log is deliberately PER-WORKTREE (one appender each):
12
+ *
13
+ * primary clone → `<primary>/.webpieces/logs/branch-mutations.log`
14
+ * linked worktree → `<primary>/.webpieces/worktrees/<name>/logs/branch-mutations.log`
15
+ *
16
+ * So in a linked worktree the doc named a file that DOES NOT EXIST. The reader greps nothing, and the
17
+ * silence reads as "no deletions were logged" — the opposite of the truth, from the one file whose
18
+ * entire job is to prove every deletion is recoverable.
19
+ *
20
+ * The cure is not "fix that line". Every one of these paths already has a resolver, and the template
21
+ * engine already substitutes (`{{MERGE_DIR}}`, `{{RUN_STATE}}`, `{{BRANCH_MUTATION_LOG}}` …). This
22
+ * rule makes RESTATING the thing that fails and RENDERING the thing that passes, so the next one
23
+ * cannot land.
24
+ *
25
+ * ## Scope
26
+ *
27
+ * `templateDirs` (workspace-relative dirs, recursive, `.md` only) — default
28
+ * `packages/tooling/rules-config/templates`. A repo that generates no AI docs matches no files and the
29
+ * rule is a no-op. Deliberately NOT widened to `backlog/**` (frozen records of past requests — editing
30
+ * them falsifies the record) or to hand-written docs that name a FILE rather than a path.
31
+ *
32
+ * MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines only) | NEW_AND_MODIFIED_FILES (whole changed file).
33
+ * Diff-scoped by default, so the docs whose SUBJECT is the layout (they print both rows on purpose)
34
+ * are not retroactively flooded — the rule bites when a template is next edited.
35
+ *
36
+ * ESCAPE HATCH (this table IS the layout, and printing it is the point):
37
+ * <!-- webpieces-disable no-state-paths-in-templates -- <reason> -->
38
+ *
39
+ * `~/.webpieces/…` is NOT a violation: the machine-local tier has no per-tree resolver and no
40
+ * per-tree answer, so the literal is the correct and only spelling of it.
41
+ */
42
+ import { NoStatePathsInTemplatesConfig } from '@webpieces/rules-config';
43
+ import { CodeValidator, ExecutorResult } from './code-validator';
44
+ /** One restated state path, located in a template. */
45
+ export declare class StatePathViolation {
46
+ readonly file: string;
47
+ readonly line: number;
48
+ readonly column: number;
49
+ readonly detail: string;
50
+ constructor(file: string, line: number, column: number, detail: string);
51
+ }
52
+ /** One restated state path within a single file, plus whether its line carries a disable. */
53
+ export declare class StatePathHit {
54
+ readonly line: number;
55
+ readonly column: number;
56
+ readonly detail: string;
57
+ readonly hasDisableComment: boolean;
58
+ constructor(line: number, column: number, detail: string, hasDisableComment: boolean);
59
+ }
60
+ export declare class NoStatePathsInTemplatesValidator extends CodeValidator<NoStatePathsInTemplatesConfig> {
61
+ constructor(config: NoStatePathsInTemplatesConfig);
62
+ run(workspaceRoot: string): Promise<ExecutorResult>;
63
+ /**
64
+ * The ONE spelling of "this validator failed": a thrown {@link RuleFailError} carrying its cures as
65
+ * `Option[]`, which {@link RuleReporter} catches and renders.
66
+ *
67
+ * NOT `console.error` + `{ success: false }`. That pair is a second spelling of the same fact (see
68
+ * RuleReporter.runValidators, which calls it a back-compat shim scheduled for deletion), and
69
+ * hand-written "To fix: … / Escape hatch: …" prose is a second spelling of `Option[]` — the
70
+ * framework owns the `Fix Option N:` numbering and the `(preferred)` tag, so a rule never writes them.
71
+ */
72
+ private failure;
73
+ /**
74
+ * A changed file this rule looks at: a `.md` under one of the configured template dirs.
75
+ *
76
+ * The ONE place the file set is narrowed, so both modes run over the same list and a scope change
77
+ * cannot be applied in one and forgotten in the other.
78
+ */
79
+ isRelevantFile(file: string): boolean;
80
+ /** Every restated state path in `text`, one hit per line (the first match on that line). */
81
+ findHits(text: string): StatePathHit[];
82
+ /**
83
+ * Where `prefix` is restated in `raw`, or -1.
84
+ *
85
+ * `~/.webpieces/…` is skipped: the machine-local tier is the same absolute path from every tree, so
86
+ * there is no resolver to call and the literal is the only spelling there is. Everything else —
87
+ * including the bare relative form that started this — is a restatement of a per-tree path.
88
+ */
89
+ private columnOf;
90
+ /** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */
91
+ private violationsForModifiedCode;
92
+ /** NEW_AND_MODIFIED_FILES: every hit in any changed template. */
93
+ private violationsForModifiedFiles;
94
+ private hitsForFile;
95
+ private templateDirs;
96
+ private bannedPrefixes;
97
+ /** True if a `webpieces-disable no-state-paths-in-templates` sits on this line or up to 3 above. */
98
+ private hasDisableComment;
99
+ private resolveMode;
100
+ }
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ /**
3
+ * Validate No State Paths In Templates
4
+ *
5
+ * FAILS when a GENERATED-doc template restates a webpieces state path (`.webpieces/…`) as a literal
6
+ * instead of rendering it from the resolver that already computes it.
7
+ *
8
+ * ## The incident this exists for
9
+ *
10
+ * `webpieces.git-workflow.md` — an AI-instruction doc regenerated into `.webpieces/instruct-ai/` of
11
+ * every governed repo on every `wp-*` command — carried the literal
12
+ * `.webpieces/logs/branch-mutations.log`. That log is deliberately PER-WORKTREE (one appender each):
13
+ *
14
+ * primary clone → `<primary>/.webpieces/logs/branch-mutations.log`
15
+ * linked worktree → `<primary>/.webpieces/worktrees/<name>/logs/branch-mutations.log`
16
+ *
17
+ * So in a linked worktree the doc named a file that DOES NOT EXIST. The reader greps nothing, and the
18
+ * silence reads as "no deletions were logged" — the opposite of the truth, from the one file whose
19
+ * entire job is to prove every deletion is recoverable.
20
+ *
21
+ * The cure is not "fix that line". Every one of these paths already has a resolver, and the template
22
+ * engine already substitutes (`{{MERGE_DIR}}`, `{{RUN_STATE}}`, `{{BRANCH_MUTATION_LOG}}` …). This
23
+ * rule makes RESTATING the thing that fails and RENDERING the thing that passes, so the next one
24
+ * cannot land.
25
+ *
26
+ * ## Scope
27
+ *
28
+ * `templateDirs` (workspace-relative dirs, recursive, `.md` only) — default
29
+ * `packages/tooling/rules-config/templates`. A repo that generates no AI docs matches no files and the
30
+ * rule is a no-op. Deliberately NOT widened to `backlog/**` (frozen records of past requests — editing
31
+ * them falsifies the record) or to hand-written docs that name a FILE rather than a path.
32
+ *
33
+ * MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines only) | NEW_AND_MODIFIED_FILES (whole changed file).
34
+ * Diff-scoped by default, so the docs whose SUBJECT is the layout (they print both rows on purpose)
35
+ * are not retroactively flooded — the rule bites when a template is next edited.
36
+ *
37
+ * ESCAPE HATCH (this table IS the layout, and printing it is the point):
38
+ * <!-- webpieces-disable no-state-paths-in-templates -- <reason> -->
39
+ *
40
+ * `~/.webpieces/…` is NOT a violation: the machine-local tier has no per-tree resolver and no
41
+ * per-tree answer, so the literal is the correct and only spelling of it.
42
+ */
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.NoStatePathsInTemplatesValidator = exports.StatePathHit = exports.StatePathViolation = void 0;
45
+ const tslib_1 = require("tslib");
46
+ const fs = tslib_1.__importStar(require("fs"));
47
+ const path = tslib_1.__importStar(require("path"));
48
+ const rules_config_1 = require("@webpieces/rules-config");
49
+ const inversify_1 = require("inversify");
50
+ const code_validator_1 = require("./code-validator");
51
+ const resolve_mode_1 = require("./resolve-mode");
52
+ /** One restated state path, located in a template. */
53
+ class StatePathViolation {
54
+ file;
55
+ line;
56
+ column;
57
+ detail;
58
+ constructor(file, line, column, detail) {
59
+ this.file = file;
60
+ this.line = line;
61
+ this.column = column;
62
+ this.detail = detail;
63
+ }
64
+ }
65
+ exports.StatePathViolation = StatePathViolation;
66
+ /** One restated state path within a single file, plus whether its line carries a disable. */
67
+ class StatePathHit {
68
+ line;
69
+ column;
70
+ detail;
71
+ hasDisableComment;
72
+ constructor(line, column, detail, hasDisableComment) {
73
+ this.line = line;
74
+ this.column = column;
75
+ this.detail = detail;
76
+ this.hasDisableComment = hasDisableComment;
77
+ }
78
+ }
79
+ exports.StatePathHit = StatePathHit;
80
+ let NoStatePathsInTemplatesValidator = class NoStatePathsInTemplatesValidator extends code_validator_1.CodeValidator {
81
+ constructor(config) {
82
+ super(config, rules_config_1.RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES, rules_config_1.RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES);
83
+ }
84
+ async run(workspaceRoot) {
85
+ const opts = this.config;
86
+ const mode = this.resolveMode(opts.mode ?? 'NEW_AND_MODIFIED_CODE', opts.turnOffRuleUntilEpoch, opts.turnOffRuleWhileOnBranch ?? undefined);
87
+ const disableAllowed = opts.disableAllowed ?? true;
88
+ if (mode === 'OFF') {
89
+ console.log('\n⏭️ Skipping no-state-paths-in-templates validation (mode: OFF)\n');
90
+ return { success: true };
91
+ }
92
+ console.log('\n📏 Validating No State Paths In Templates\n');
93
+ console.log(` Mode: ${mode}`);
94
+ let base = process.env['NX_BASE'];
95
+ const head = process.env['NX_HEAD'];
96
+ if (base === undefined || base === '') {
97
+ base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
98
+ if (base === undefined || base === '') {
99
+ console.log('\n⏭️ Skipping no-state-paths-in-templates validation (could not detect base branch)\n');
100
+ return { success: true };
101
+ }
102
+ }
103
+ console.log(` Base: ${base}`);
104
+ console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\n`);
105
+ const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head, { tsOnly: false })
106
+ .filter((file) => this.isRelevantFile(file));
107
+ if (changedFiles.length === 0) {
108
+ console.log('✅ No generated-doc templates changed');
109
+ return { success: true };
110
+ }
111
+ console.log(`📂 Checking ${String(changedFiles.length)} changed template(s)...`);
112
+ const violations = mode === 'NEW_AND_MODIFIED_CODE'
113
+ ? this.violationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed)
114
+ : this.violationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
115
+ if (violations.length === 0) {
116
+ console.log('✅ No hard-coded state paths in generated-doc templates');
117
+ return { success: true };
118
+ }
119
+ throw this.failure(violations, mode);
120
+ }
121
+ /**
122
+ * The ONE spelling of "this validator failed": a thrown {@link RuleFailError} carrying its cures as
123
+ * `Option[]`, which {@link RuleReporter} catches and renders.
124
+ *
125
+ * NOT `console.error` + `{ success: false }`. That pair is a second spelling of the same fact (see
126
+ * RuleReporter.runValidators, which calls it a back-compat shim scheduled for deletion), and
127
+ * hand-written "To fix: … / Escape hatch: …" prose is a second spelling of `Option[]` — the
128
+ * framework owns the `Fix Option N:` numbering and the `(preferred)` tag, so a rule never writes them.
129
+ */
130
+ failure(violations, mode) {
131
+ const detail = violations.map((violation) => ` ${violation.file}:${String(violation.line)}:${String(violation.column)} restates \`${violation.detail}…\``)
132
+ .join('\n');
133
+ const first = violations[0];
134
+ return new rules_config_1.RuleFailError(rules_config_1.RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES, 'A generated-doc template RESTATES a webpieces state path instead of computing it. These docs are '
135
+ + 'regenerated into every governed repo and handed to an agent by absolute path AS INSTRUCTION, and '
136
+ + 'every .webpieces/ path is per-tree — a linked worktree keeps its state under '
137
+ + `<primary>/.webpieces/worktrees/<name>/ — so the relative spelling names a file that DOES NOT EXIST `
138
+ + `in half the trees that read it. Current mode: ${mode}.\n${detail}`, first?.line, first?.detail, [
139
+ new rules_config_1.Option('Put a {{PLACEHOLDER}} in the template and render it from the resolver that already computes '
140
+ + 'that path, the way {{BRANCH_MUTATION_LOG}} is rendered from '
141
+ + 'BranchMutationLog.branchMutationLogPath(root) in GitWorkflowDoc.render', true),
142
+ new rules_config_1.Option('If the doc\'s SUBJECT is the layout and printing the literal is the point, say so out loud:\n'
143
+ + '<!-- webpieces-disable no-state-paths-in-templates -- <reason> -->'),
144
+ ]);
145
+ }
146
+ /**
147
+ * A changed file this rule looks at: a `.md` under one of the configured template dirs.
148
+ *
149
+ * The ONE place the file set is narrowed, so both modes run over the same list and a scope change
150
+ * cannot be applied in one and forgotten in the other.
151
+ */
152
+ isRelevantFile(file) {
153
+ const norm = file.replace(/\\/g, '/');
154
+ if (!norm.endsWith('.md'))
155
+ return false;
156
+ return this.templateDirs().some((dir) => norm.startsWith(`${dir}/`));
157
+ }
158
+ /** Every restated state path in `text`, one hit per line (the first match on that line). */
159
+ findHits(text) {
160
+ const lines = text.split('\n');
161
+ const hits = [];
162
+ for (let i = 0; i < lines.length; i++) {
163
+ const raw = lines[i] ?? '';
164
+ for (const prefix of this.bannedPrefixes()) {
165
+ const column = this.columnOf(raw, prefix);
166
+ if (column < 0)
167
+ continue;
168
+ hits.push(new StatePathHit(i + 1, column + 1, prefix, this.hasDisableComment(lines, i + 1)));
169
+ break;
170
+ }
171
+ }
172
+ return hits;
173
+ }
174
+ /**
175
+ * Where `prefix` is restated in `raw`, or -1.
176
+ *
177
+ * `~/.webpieces/…` is skipped: the machine-local tier is the same absolute path from every tree, so
178
+ * there is no resolver to call and the literal is the only spelling there is. Everything else —
179
+ * including the bare relative form that started this — is a restatement of a per-tree path.
180
+ */
181
+ columnOf(raw, prefix) {
182
+ let from = 0;
183
+ for (;;) {
184
+ const at = raw.indexOf(prefix, from);
185
+ if (at < 0)
186
+ return -1;
187
+ if (raw.slice(Math.max(0, at - 2), at) !== '~/')
188
+ return at;
189
+ from = at + 1;
190
+ }
191
+ }
192
+ /** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */
193
+ violationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
194
+ const violations = [];
195
+ for (const file of changedFiles) {
196
+ const changedLines = (0, rules_config_1.getChangedLineNumbers)((0, rules_config_1.getFileDiff)(workspaceRoot, file, base, head));
197
+ if (changedLines.size === 0)
198
+ continue;
199
+ for (const hit of this.hitsForFile(file, workspaceRoot)) {
200
+ if (disableAllowed && hit.hasDisableComment)
201
+ continue;
202
+ if (!changedLines.has(hit.line))
203
+ continue;
204
+ violations.push(new StatePathViolation(file, hit.line, hit.column, hit.detail));
205
+ }
206
+ }
207
+ return violations;
208
+ }
209
+ /** NEW_AND_MODIFIED_FILES: every hit in any changed template. */
210
+ violationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
211
+ const violations = [];
212
+ for (const file of changedFiles) {
213
+ for (const hit of this.hitsForFile(file, workspaceRoot)) {
214
+ if (disableAllowed && hit.hasDisableComment)
215
+ continue;
216
+ violations.push(new StatePathViolation(file, hit.line, hit.column, hit.detail));
217
+ }
218
+ }
219
+ return violations;
220
+ }
221
+ hitsForFile(file, workspaceRoot) {
222
+ const fullPath = path.join(workspaceRoot, file);
223
+ if (!fs.existsSync(fullPath))
224
+ return [];
225
+ return this.findHits(fs.readFileSync(fullPath, 'utf-8'));
226
+ }
227
+ templateDirs() {
228
+ const dirs = this.config.templateDirs ?? rules_config_1.DEFAULT_TEMPLATE_DIRS;
229
+ return dirs.map((dir) => dir.replace(/\\/g, '/').replace(/\/+$/, ''));
230
+ }
231
+ bannedPrefixes() {
232
+ return this.config.bannedPathPrefixes ?? rules_config_1.DEFAULT_BANNED_STATE_PATH_PREFIXES;
233
+ }
234
+ /** True if a `webpieces-disable no-state-paths-in-templates` sits on this line or up to 3 above. */
235
+ hasDisableComment(lines, lineNumber) {
236
+ const start = Math.max(0, lineNumber - 4);
237
+ for (let i = lineNumber - 1; i >= start; i--) {
238
+ if ((0, rules_config_1.hasDisable)(lines[i] ?? '', rules_config_1.RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES))
239
+ return true;
240
+ }
241
+ return false;
242
+ }
243
+ resolveMode(normalMode, epoch, branchPattern) {
244
+ if (normalMode === 'OFF')
245
+ return normalMode;
246
+ const skip = (0, resolve_mode_1.shouldSkipRule)(epoch, branchPattern);
247
+ if (skip.skip) {
248
+ console.log(`\n⏭️ Skipping no-state-paths-in-templates validation (${skip.reason})\n`);
249
+ return 'OFF';
250
+ }
251
+ return normalMode;
252
+ }
253
+ };
254
+ exports.NoStatePathsInTemplatesValidator = NoStatePathsInTemplatesValidator;
255
+ exports.NoStatePathsInTemplatesValidator = NoStatePathsInTemplatesValidator = tslib_1.__decorate([
256
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
257
+ tslib_1.__metadata("design:paramtypes", [rules_config_1.NoStatePathsInTemplatesConfig])
258
+ ], NoStatePathsInTemplatesValidator);
259
+ //# sourceMappingURL=validate-no-state-paths-in-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-no-state-paths-in-templates.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-no-state-paths-in-templates.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;;;;AAEH,+CAAyB;AACzB,mDAA6B;AAC7B,0DAaiC;AACjC,yCAA2D;AAE3D,qDAAiE;AACjE,iDAAgD;AAEhD,sDAAsD;AACtD,MAAa,kBAAkB;IAEd;IACA;IACA;IACA;IAJb,YACa,IAAY,EACZ,IAAY,EACZ,MAAc,EACd,MAAc;QAHd,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAQ;IACxB,CAAC;CACP;AAPD,gDAOC;AAED,6FAA6F;AAC7F,MAAa,YAAY;IAER;IACA;IACA;IACA;IAJb,YACa,IAAY,EACZ,MAAc,EACd,MAAc,EACd,iBAA0B;QAH1B,SAAI,GAAJ,IAAI,CAAQ;QACZ,WAAM,GAAN,MAAM,CAAQ;QACd,WAAM,GAAN,MAAM,CAAQ;QACd,sBAAiB,GAAjB,iBAAiB,CAAS;IACpC,CAAC;CACP;AAPD,oCAOC;AAGM,IAAM,gCAAgC,GAAtC,MAAM,gCAAiC,SAAQ,8BAA4C;IAC9F,YAAY,MAAqC;QAC7C,KAAK,CAAC,MAAM,EAAE,yBAAU,CAAC,2BAA2B,EAAE,yBAAU,CAAC,2BAA2B,CAAC,CAAC;IAClG,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,aAAqB;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CACzB,IAAI,CAAC,IAAI,IAAI,uBAAuB,EAAE,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,wBAAwB,IAAI,SAAS,CAAC,CAAC;QAClH,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC;QACnD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC;YACnF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAEhC,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YACpC,IAAI,GAAG,IAAA,yBAAU,EAAC,aAAa,CAAC,IAAI,SAAS,CAAC;YAC9C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBACpC,OAAO,CAAC,GAAG,CAAC,wFAAwF,CAAC,CAAC;gBACtG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC7B,CAAC;QACL,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,6CAA6C,IAAI,CAAC,CAAC;QAEnF,MAAM,YAAY,GAAG,IAAA,8BAAe,EAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;aAC7E,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;YACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC;QAEjF,MAAM,UAAU,GAAG,IAAI,KAAK,uBAAuB;YAC/C,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;YACzF,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,aAAa,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;QAEnF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;YACtE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IAED;;;;;;;;OAQG;IACK,OAAO,CAAC,UAAgC,EAAE,IAAsB;QACpE,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAA6B,EAAU,EAAE,CACpE,KAAK,SAAS,CAAC,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,SAAS,CAAC,MAAM,KAAK,CAAC;aAC7G,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,OAAO,IAAI,4BAAa,CACpB,yBAAU,CAAC,2BAA2B,EACtC,mGAAmG;cACjG,mGAAmG;cACnG,+EAA+E;cAC/E,qGAAqG;cACrG,iDAAiD,IAAI,MAAM,MAAM,EAAE,EACrE,KAAK,EAAE,IAAI,EACX,KAAK,EAAE,MAAM,EACb;YACI,IAAI,qBAAM,CACN,8FAA8F;kBAC5F,8DAA8D;kBAC9D,wEAAwE,EAC1E,IAAI,CAAC;YACT,IAAI,qBAAM,CACN,+FAA+F;kBAC7F,oEAAoE,CAAC;SAC9E,CACJ,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,IAAY;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACxC,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,GAAW,EAAW,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,4FAA4F;IAC5F,QAAQ,CAAC,IAAY;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAmB,EAAE,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;gBACzC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAC1C,IAAI,MAAM,GAAG,CAAC;oBAAE,SAAS;gBACzB,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7F,MAAM;YACV,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACK,QAAQ,CAAC,GAAW,EAAE,MAAc;QACxC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,SAAS,CAAC;YACN,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACrC,IAAI,EAAE,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI;gBAAE,OAAO,EAAE,CAAC;YAC3D,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;QAClB,CAAC;IACL,CAAC;IAED,wEAAwE;IAChE,yBAAyB,CAC7B,aAAqB,EACrB,YAAsB,EACtB,IAAY,EACZ,IAAwB,EACxB,cAAuB;QAEvB,MAAM,UAAU,GAAyB,EAAE,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAC9B,MAAM,YAAY,GAAG,IAAA,oCAAqB,EAAC,IAAA,0BAAW,EAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YACzF,IAAI,YAAY,CAAC,IAAI,KAAK,CAAC;gBAAE,SAAS;YACtC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;gBACtD,IAAI,cAAc,IAAI,GAAG,CAAC,iBAAiB;oBAAE,SAAS;gBACtD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC1C,UAAU,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YACpF,CAAC;QACL,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,iEAAiE;IACzD,0BAA0B,CAC9B,aAAqB,EACrB,YAAsB,EACtB,cAAuB;QAEvB,MAAM,UAAU,GAAyB,EAAE,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;gBACtD,IAAI,cAAc,IAAI,GAAG,CAAC,iBAAiB;oBAAE,SAAS;gBACtD,UAAU,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YACpF,CAAC;QACL,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;IAEO,WAAW,CAAC,IAAY,EAAE,aAAqB;QACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC;IAEO,YAAY;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,oCAAqB,CAAC;QAC/D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAW,EAAU,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1F,CAAC;IAEO,cAAc;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,iDAAkC,CAAC;IAChF,CAAC;IAED,oGAAoG;IAC5F,iBAAiB,CAAC,KAAe,EAAE,UAAkB;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;QAC1C,KAAK,IAAI,CAAC,GAAG,UAAU,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,IAAI,IAAA,yBAAU,EAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,yBAAU,CAAC,2BAA2B,CAAC;gBAAE,OAAO,IAAI,CAAC;QACxF,CAAC;QACD,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,WAAW,CACf,UAA4B,EAC5B,KAAyB,EACzB,aAAiC;QAEjC,IAAI,UAAU,KAAK,KAAK;YAAE,OAAO,UAAU,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAA,6BAAc,EAAC,KAAK,EAAE,aAAa,CAAC,CAAC;QAClD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,0DAA0D,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;YACxF,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,UAAU,CAAC;IACtB,CAAC;CACJ,CAAA;AA5MY,4EAAgC;2CAAhC,gCAAgC;IAD5C,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAEjB,4CAA6B;GADxC,gCAAgC,CA4M5C","sourcesContent":["/**\n * Validate No State Paths In Templates\n *\n * FAILS when a GENERATED-doc template restates a webpieces state path (`.webpieces/…`) as a literal\n * instead of rendering it from the resolver that already computes it.\n *\n * ## The incident this exists for\n *\n * `webpieces.git-workflow.md` — an AI-instruction doc regenerated into `.webpieces/instruct-ai/` of\n * every governed repo on every `wp-*` command — carried the literal\n * `.webpieces/logs/branch-mutations.log`. That log is deliberately PER-WORKTREE (one appender each):\n *\n * primary clone → `<primary>/.webpieces/logs/branch-mutations.log`\n * linked worktree → `<primary>/.webpieces/worktrees/<name>/logs/branch-mutations.log`\n *\n * So in a linked worktree the doc named a file that DOES NOT EXIST. The reader greps nothing, and the\n * silence reads as \"no deletions were logged\" — the opposite of the truth, from the one file whose\n * entire job is to prove every deletion is recoverable.\n *\n * The cure is not \"fix that line\". Every one of these paths already has a resolver, and the template\n * engine already substitutes (`{{MERGE_DIR}}`, `{{RUN_STATE}}`, `{{BRANCH_MUTATION_LOG}}` …). This\n * rule makes RESTATING the thing that fails and RENDERING the thing that passes, so the next one\n * cannot land.\n *\n * ## Scope\n *\n * `templateDirs` (workspace-relative dirs, recursive, `.md` only) — default\n * `packages/tooling/rules-config/templates`. A repo that generates no AI docs matches no files and the\n * rule is a no-op. Deliberately NOT widened to `backlog/**` (frozen records of past requests — editing\n * them falsifies the record) or to hand-written docs that name a FILE rather than a path.\n *\n * MODES: OFF | NEW_AND_MODIFIED_CODE (changed lines only) | NEW_AND_MODIFIED_FILES (whole changed file).\n * Diff-scoped by default, so the docs whose SUBJECT is the layout (they print both rows on purpose)\n * are not retroactively flooded — the rule bites when a template is next edited.\n *\n * ESCAPE HATCH (this table IS the layout, and printing it is the point):\n * <!-- webpieces-disable no-state-paths-in-templates -- <reason> -->\n *\n * `~/.webpieces/…` is NOT a violation: the machine-local tier has no per-tree resolver and no\n * per-tree answer, so the literal is the correct and only spelling of it.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n hasDisable,\n RULE_NAMES,\n NoStatePathsInTemplatesConfig,\n Option,\n RuleFailError,\n DEFAULT_TEMPLATE_DIRS,\n DEFAULT_BANNED_STATE_PATH_PREFIXES,\n ModifiedCodeMode,\n detectBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\n/** One restated state path, located in a template. */\nexport class StatePathViolation {\n constructor(\n readonly file: string,\n readonly line: number,\n readonly column: number,\n readonly detail: string,\n ) {}\n}\n\n/** One restated state path within a single file, plus whether its line carries a disable. */\nexport class StatePathHit {\n constructor(\n readonly line: number,\n readonly column: number,\n readonly detail: string,\n readonly hasDisableComment: boolean,\n ) {}\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class NoStatePathsInTemplatesValidator extends CodeValidator<NoStatePathsInTemplatesConfig> {\n constructor(config: NoStatePathsInTemplatesConfig) {\n super(config, RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES, RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES);\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n const opts = this.config;\n const mode = this.resolveMode(\n opts.mode ?? 'NEW_AND_MODIFIED_CODE', opts.turnOffRuleUntilEpoch, opts.turnOffRuleWhileOnBranch ?? undefined);\n const disableAllowed = opts.disableAllowed ?? true;\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping no-state-paths-in-templates validation (mode: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Validating No State Paths In Templates\\n');\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n if (base === undefined || base === '') {\n base = detectBase(workspaceRoot) ?? undefined;\n if (base === undefined || base === '') {\n console.log('\\n⏭️ Skipping no-state-paths-in-templates validation (could not detect base branch)\\n');\n return { success: true };\n }\n }\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}\\n`);\n\n const changedFiles = getChangedFiles(workspaceRoot, base, head, { tsOnly: false })\n .filter((file: string): boolean => this.isRelevantFile(file));\n if (changedFiles.length === 0) {\n console.log('✅ No generated-doc templates changed');\n return { success: true };\n }\n console.log(`📂 Checking ${String(changedFiles.length)} changed template(s)...`);\n\n const violations = mode === 'NEW_AND_MODIFIED_CODE'\n ? this.violationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed)\n : this.violationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);\n\n if (violations.length === 0) {\n console.log('✅ No hard-coded state paths in generated-doc templates');\n return { success: true };\n }\n throw this.failure(violations, mode);\n }\n\n /**\n * The ONE spelling of \"this validator failed\": a thrown {@link RuleFailError} carrying its cures as\n * `Option[]`, which {@link RuleReporter} catches and renders.\n *\n * NOT `console.error` + `{ success: false }`. That pair is a second spelling of the same fact (see\n * RuleReporter.runValidators, which calls it a back-compat shim scheduled for deletion), and\n * hand-written \"To fix: … / Escape hatch: …\" prose is a second spelling of `Option[]` — the\n * framework owns the `Fix Option N:` numbering and the `(preferred)` tag, so a rule never writes them.\n */\n private failure(violations: StatePathViolation[], mode: ModifiedCodeMode): RuleFailError {\n const detail = violations.map((violation: StatePathViolation): string =>\n ` ${violation.file}:${String(violation.line)}:${String(violation.column)} restates \\`${violation.detail}…\\``)\n .join('\\n');\n const first = violations[0];\n return new RuleFailError(\n RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES,\n 'A generated-doc template RESTATES a webpieces state path instead of computing it. These docs are '\n + 'regenerated into every governed repo and handed to an agent by absolute path AS INSTRUCTION, and '\n + 'every .webpieces/ path is per-tree — a linked worktree keeps its state under '\n + `<primary>/.webpieces/worktrees/<name>/ — so the relative spelling names a file that DOES NOT EXIST `\n + `in half the trees that read it. Current mode: ${mode}.\\n${detail}`,\n first?.line,\n first?.detail,\n [\n new Option(\n 'Put a {{PLACEHOLDER}} in the template and render it from the resolver that already computes '\n + 'that path, the way {{BRANCH_MUTATION_LOG}} is rendered from '\n + 'BranchMutationLog.branchMutationLogPath(root) in GitWorkflowDoc.render',\n true),\n new Option(\n 'If the doc\\'s SUBJECT is the layout and printing the literal is the point, say so out loud:\\n'\n + '<!-- webpieces-disable no-state-paths-in-templates -- <reason> -->'),\n ],\n );\n }\n\n /**\n * A changed file this rule looks at: a `.md` under one of the configured template dirs.\n *\n * The ONE place the file set is narrowed, so both modes run over the same list and a scope change\n * cannot be applied in one and forgotten in the other.\n */\n isRelevantFile(file: string): boolean {\n const norm = file.replace(/\\\\/g, '/');\n if (!norm.endsWith('.md')) return false;\n return this.templateDirs().some((dir: string): boolean => norm.startsWith(`${dir}/`));\n }\n\n /** Every restated state path in `text`, one hit per line (the first match on that line). */\n findHits(text: string): StatePathHit[] {\n const lines = text.split('\\n');\n const hits: StatePathHit[] = [];\n for (let i = 0; i < lines.length; i++) {\n const raw = lines[i] ?? '';\n for (const prefix of this.bannedPrefixes()) {\n const column = this.columnOf(raw, prefix);\n if (column < 0) continue;\n hits.push(new StatePathHit(i + 1, column + 1, prefix, this.hasDisableComment(lines, i + 1)));\n break;\n }\n }\n return hits;\n }\n\n /**\n * Where `prefix` is restated in `raw`, or -1.\n *\n * `~/.webpieces/…` is skipped: the machine-local tier is the same absolute path from every tree, so\n * there is no resolver to call and the literal is the only spelling there is. Everything else —\n * including the bare relative form that started this — is a restatement of a per-tree path.\n */\n private columnOf(raw: string, prefix: string): number {\n let from = 0;\n for (;;) {\n const at = raw.indexOf(prefix, from);\n if (at < 0) return -1;\n if (raw.slice(Math.max(0, at - 2), at) !== '~/') return at;\n from = at + 1;\n }\n }\n\n /** NEW_AND_MODIFIED_CODE: only hits whose line is in the diff hunks. */\n private violationsForModifiedCode(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n head: string | undefined,\n disableAllowed: boolean,\n ): StatePathViolation[] {\n const violations: StatePathViolation[] = [];\n for (const file of changedFiles) {\n const changedLines = getChangedLineNumbers(getFileDiff(workspaceRoot, file, base, head));\n if (changedLines.size === 0) continue;\n for (const hit of this.hitsForFile(file, workspaceRoot)) {\n if (disableAllowed && hit.hasDisableComment) continue;\n if (!changedLines.has(hit.line)) continue;\n violations.push(new StatePathViolation(file, hit.line, hit.column, hit.detail));\n }\n }\n return violations;\n }\n\n /** NEW_AND_MODIFIED_FILES: every hit in any changed template. */\n private violationsForModifiedFiles(\n workspaceRoot: string,\n changedFiles: string[],\n disableAllowed: boolean,\n ): StatePathViolation[] {\n const violations: StatePathViolation[] = [];\n for (const file of changedFiles) {\n for (const hit of this.hitsForFile(file, workspaceRoot)) {\n if (disableAllowed && hit.hasDisableComment) continue;\n violations.push(new StatePathViolation(file, hit.line, hit.column, hit.detail));\n }\n }\n return violations;\n }\n\n private hitsForFile(file: string, workspaceRoot: string): StatePathHit[] {\n const fullPath = path.join(workspaceRoot, file);\n if (!fs.existsSync(fullPath)) return [];\n return this.findHits(fs.readFileSync(fullPath, 'utf-8'));\n }\n\n private templateDirs(): readonly string[] {\n const dirs = this.config.templateDirs ?? DEFAULT_TEMPLATE_DIRS;\n return dirs.map((dir: string): string => dir.replace(/\\\\/g, '/').replace(/\\/+$/, ''));\n }\n\n private bannedPrefixes(): readonly string[] {\n return this.config.bannedPathPrefixes ?? DEFAULT_BANNED_STATE_PATH_PREFIXES;\n }\n\n /** True if a `webpieces-disable no-state-paths-in-templates` sits on this line or up to 3 above. */\n private hasDisableComment(lines: string[], lineNumber: number): boolean {\n const start = Math.max(0, lineNumber - 4);\n for (let i = lineNumber - 1; i >= start; i--) {\n if (hasDisable(lines[i] ?? '', RULE_NAMES.NO_STATE_PATHS_IN_TEMPLATES)) return true;\n }\n return false;\n }\n\n private resolveMode(\n normalMode: ModifiedCodeMode,\n epoch: number | undefined,\n branchPattern: string | undefined,\n ): ModifiedCodeMode {\n if (normalMode === 'OFF') return normalMode;\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping no-state-paths-in-templates validation (${skip.reason})\\n`);\n return 'OFF';\n }\n return normalMode;\n }\n}\n"]}