code-standards 7.33.0__tar.gz → 8.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. {code_standards-7.33.0 → code_standards-8.1.1}/.gitignore +1 -1
  2. {code_standards-7.33.0 → code_standards-8.1.1}/PKG-INFO +8 -5
  3. {code_standards-7.33.0 → code_standards-8.1.1}/README.md +3 -1
  4. {code_standards-7.33.0 → code_standards-8.1.1}/pyproject.toml +6 -4
  5. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/api.py +49 -27
  6. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/cli/main.py +108 -12
  7. code_standards-8.1.1/src/sarj_standards/configs/cli-reference.v1.json +1 -0
  8. code_standards-8.1.1/src/sarj_standards/configs/eslint-selected-rules.mjs +46 -0
  9. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/eslint.peers.json +2 -2
  10. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/eslint.strict.mjs +15 -9
  11. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/rule-calibration-campaign.v1.json +4 -4
  12. code_standards-8.1.1/src/sarj_standards/configs/rule-level-source.mjs +62 -0
  13. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/transaction.py +12 -3
  14. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/analysis.py +4 -1
  15. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/external.py +32 -1
  16. code_standards-8.1.1/src/sarj_standards/libs/linting/scheduling.py +43 -0
  17. code_standards-8.1.1/src/sarj_standards/libs/linting/text_rule_base.py +86 -0
  18. code_standards-8.1.1/src/sarj_standards/libs/linting/text_rules/_registry.py +12 -0
  19. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/textlint.py +13 -71
  20. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/causality.py +19 -6
  21. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/publish.py +2 -1
  22. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/tags.py +5 -0
  23. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/config_generation.py +6 -3
  24. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/docs.py +43 -3
  25. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/ruff_freshness.py +1 -0
  26. code_standards-8.1.1/src/sarj_standards/libs/repository/rule_authoring.py +311 -0
  27. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/rule_catalog_artifact.py +61 -19
  28. code_standards-8.1.1/src/sarj_standards/libs/repository/rule_checkout.py +45 -0
  29. code_standards-8.1.1/src/sarj_standards/libs/repository/rule_examples.py +173 -0
  30. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/rule_inventory_artifact.py +10 -3
  31. code_standards-8.1.1/src/sarj_standards/libs/repository/rule_level_source.py +202 -0
  32. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/rule_lifecycle.py +49 -20
  33. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/rule_maintenance.py +15 -5
  34. code_standards-8.1.1/src/sarj_standards/libs/repository/rule_registration.py +235 -0
  35. code_standards-8.1.1/src/sarj_standards/libs/rules/contracts.py +21 -0
  36. code_standards-8.1.1/src/sarj_standards/py.typed +0 -0
  37. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/schemas/rule-catalog.v1.json +1 -1
  38. code_standards-7.33.0/src/sarj_standards/configs/cli-reference.v1.json +0 -1
  39. code_standards-7.33.0/src/sarj_standards/libs/repository/rule_authoring.py +0 -247
  40. code_standards-7.33.0/src/sarj_standards/libs/repository/rule_level_source.py +0 -117
  41. code_standards-7.33.0/src/sarj_standards/libs/rules/contracts.py +0 -395
  42. {code_standards-7.33.0 → code_standards-8.1.1}/LICENSE +0 -0
  43. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/__init__.py +0 -0
  44. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/__main__.py +0 -0
  45. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/_meta.py +0 -0
  46. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/cli/__init__.py +0 -0
  47. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/Mintfile.mobile.strict +0 -0
  48. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/basedpyright.python315-watch.json +0 -0
  49. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/basedpyright.strict.json +0 -0
  50. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/detekt.strict.yml +0 -0
  51. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/doctor.config.json +0 -0
  52. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/eslint-compact-formatter.mjs +0 -0
  53. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/eslint.application.mjs +0 -0
  54. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/ktlint.strict.editorconfig +0 -0
  55. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/markdownlint.strict.yaml +0 -0
  56. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/mobile-tools.versions.json +0 -0
  57. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/mobsf.strict.yml +0 -0
  58. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/pyright.strict.json +0 -0
  59. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/ruff.application.toml +0 -0
  60. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/ruff.python315-watch.toml +0 -0
  61. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/ruff.strict.toml +0 -0
  62. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/rule-inventory.v1.json +0 -0
  63. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/rule-ledger.json +0 -0
  64. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/shellcheck.strict.rc +0 -0
  65. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/swiftformat.strict +0 -0
  66. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/swiftlint.strict.yml +0 -0
  67. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/taplo.strict.toml +0 -0
  68. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/configs/yamllint.strict.yaml +0 -0
  69. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/__init__.py +0 -0
  70. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/__init__.py +0 -0
  71. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/configs.py +0 -0
  72. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/doctor.py +0 -0
  73. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/exclusions.py +0 -0
  74. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/hooks.py +0 -0
  75. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/launcher.py +0 -0
  76. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/lifecycle.py +0 -0
  77. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/manifest.py +0 -0
  78. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/packagemanager.py +0 -0
  79. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/retired_suppressions.py +0 -0
  80. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/scaffold.py +0 -0
  81. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/service.py +0 -0
  82. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/upgrade.py +0 -0
  83. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/adoption/uvtool.py +0 -0
  84. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/catalogs/__init__.py +0 -0
  85. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/catalogs/slack_automations.py +0 -0
  86. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/corpus/__init__.py +0 -0
  87. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/corpus/manifest.py +0 -0
  88. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/corpus/snapshot.py +0 -0
  89. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/diagnostics/__init__.py +0 -0
  90. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/diagnostics/analysis.schema.json +0 -0
  91. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/diagnostics/baseline.py +0 -0
  92. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/diagnostics/models.py +0 -0
  93. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/diagnostics/serialize.py +0 -0
  94. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/diagnostics/source.py +0 -0
  95. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/filesystem.py +0 -0
  96. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/json_boundary.py +0 -0
  97. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/__init__.py +0 -0
  98. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/library_policy.py +0 -0
  99. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/mobile_tools.py +0 -0
  100. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/policy.py +0 -0
  101. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/repo_standards.py +0 -0
  102. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/linting/runner.py +0 -0
  103. {code_standards-7.33.0/src/sarj_standards/libs/repository → code_standards-8.1.1/src/sarj_standards/libs/linting/text_rules}/__init__.py +0 -0
  104. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/__init__.py +0 -0
  105. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/_values.py +0 -0
  106. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/artifacts.py +0 -0
  107. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/changes.py +0 -0
  108. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/process.py +0 -0
  109. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/registry.py +0 -0
  110. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/release_age.py +0 -0
  111. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/retirement.py +0 -0
  112. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/rollout.py +0 -0
  113. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/release/typescript.py +0 -0
  114. /code_standards-7.33.0/src/sarj_standards/py.typed → /code_standards-8.1.1/src/sarj_standards/libs/repository/__init__.py +0 -0
  115. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/cli_reference_artifact.py +0 -0
  116. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/comment_corpus.py +0 -0
  117. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/hooks.py +0 -0
  118. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/ledger.py +0 -0
  119. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/repository.py +0 -0
  120. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/rule_changes.py +0 -0
  121. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/repository/third_party_catalog_artifact.py +0 -0
  122. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/rules/__init__.py +0 -0
  123. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/rules/catalog.py +0 -0
  124. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/rules/corpus_runner.py +0 -0
  125. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/rules/evaluation.py +0 -0
  126. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/setup/__init__.py +0 -0
  127. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/setup/repository.py +0 -0
  128. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/typed_containers.py +0 -0
  129. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/libs/yaml_boundary.py +0 -0
  130. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/schemas/__init__.py +0 -0
  131. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/schemas/_paths.py +0 -0
  132. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/schemas/rule-catalog.v1.schema.json +0 -0
  133. {code_standards-7.33.0 → code_standards-8.1.1}/src/sarj_standards/schemas/slack-automations.v1.schema.json +0 -0
@@ -1,4 +1,4 @@
1
- node_modules/
1
+ node_modules
2
2
  .astro/
3
3
  dist/
4
4
  build/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code-standards
3
- Version: 7.33.0
3
+ Version: 8.1.1
4
4
  Summary: One deterministic quality gate for Sarj Python, TypeScript, Swift, Kotlin, shell, SQL, IaC, and repository policy.
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/cli/
@@ -27,9 +27,10 @@ Requires-Dist: pydantic==2.13.5
27
27
  Requires-Dist: pyyaml==6.0.3
28
28
  Requires-Dist: repo-standards==6.0.1
29
29
  Requires-Dist: ruff==0.16.8
30
- Requires-Dist: sarj-iac-lint==0.15.13
31
- Requires-Dist: sarj-python-lint==0.88.0
32
- Requires-Dist: sarj-sql-lint==0.17.7
30
+ Requires-Dist: sarj-iac-lint==0.16.0
31
+ Requires-Dist: sarj-python-lint==1.1.1
32
+ Requires-Dist: sarj-rule-contracts==0.1.0
33
+ Requires-Dist: sarj-sql-lint==0.18.0
33
34
  Requires-Dist: semgrep==1.178.0
34
35
  Requires-Dist: shellcheck-py==0.11.0.1; (sys_platform == 'darwin' and platform_machine == 'arm64') or (sys_platform == 'darwin' and platform_machine == 'x86_64') or (sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64')
35
36
  Requires-Dist: typer==0.27.2
@@ -56,6 +57,8 @@ code-standards doctor
56
57
  code-standards update
57
58
  ```
58
59
 
59
- Use repeatable `check --rule ENGINE:ID` selectors to check only selected Sarj custom rules. Exclusions, baselines, and severities still apply; upstream rules such as Ruff IDs are not supported. Native linters execute only selected rules; ESLint runs its configured rules and filters the findings.
60
+ Use repeatable `check --rule ENGINE:ID` selectors to check only selected Sarj custom rules. Exclusions, baselines, and severities still apply; upstream rules such as Ruff IDs are not supported. Native linters and ESLint execute only selected rules. ESLint retains configured parsers, options, severities, and suppression directives.
61
+
62
+ `check --jobs 2` overlaps native analysis with the external-tool pipeline. The default is `--jobs 1`; external tools remain sequential and report ordering is deterministic.
60
63
 
61
64
  [Documentation](https://code-standards.sarj.ai/) · [Source](https://github.com/sarj-ai/code-standards)
@@ -19,6 +19,8 @@ code-standards doctor
19
19
  code-standards update
20
20
  ```
21
21
 
22
- Use repeatable `check --rule ENGINE:ID` selectors to check only selected Sarj custom rules. Exclusions, baselines, and severities still apply; upstream rules such as Ruff IDs are not supported. Native linters execute only selected rules; ESLint runs its configured rules and filters the findings.
22
+ Use repeatable `check --rule ENGINE:ID` selectors to check only selected Sarj custom rules. Exclusions, baselines, and severities still apply; upstream rules such as Ruff IDs are not supported. Native linters and ESLint execute only selected rules. ESLint retains configured parsers, options, severities, and suppression directives.
23
+
24
+ `check --jobs 2` overlaps native analysis with the external-tool pipeline. The default is `--jobs 1`; external tools remain sequential and report ordering is deterministic.
23
25
 
24
26
  [Documentation](https://code-standards.sarj.ai/) · [Source](https://github.com/sarj-ai/code-standards)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "code-standards"
3
- version = "7.33.0"
3
+ version = "8.1.1"
4
4
  description = "One deterministic quality gate for Sarj Python, TypeScript, Swift, Kotlin, shell, SQL, IaC, and repository policy."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -16,6 +16,7 @@ classifiers = [
16
16
  "Topic :: Software Development :: Quality Assurance",
17
17
  ]
18
18
  dependencies = [
19
+ "sarj-rule-contracts==0.1.0",
19
20
  "pathspec==1.1.1",
20
21
  "basedpyright==1.40.1",
21
22
  "deptry==0.25.1",
@@ -28,9 +29,9 @@ dependencies = [
28
29
  "ruff==0.16.8",
29
30
  "semgrep==1.178.0",
30
31
  "shellcheck-py==0.11.0.1; (sys_platform == 'darwin' and platform_machine == 'arm64') or (sys_platform == 'darwin' and platform_machine == 'x86_64') or (sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64')",
31
- "sarj-iac-lint==0.15.13",
32
- "sarj-python-lint==0.88.0",
33
- "sarj-sql-lint==0.17.7",
32
+ "sarj-iac-lint==0.16.0",
33
+ "sarj-python-lint==1.1.1",
34
+ "sarj-sql-lint==0.18.0",
34
35
  "typer==0.27.2",
35
36
  ]
36
37
 
@@ -66,6 +67,7 @@ dev = [
66
67
  "pytest>=9.1.1",
67
68
  ]
68
69
  [tool.uv.sources]
70
+ sarj-rule-contracts = { path = "../contracts", editable = true }
69
71
  # Development uses the sibling packages while wheel smoke tests resolve the
70
72
  # same exact, already-published versions declared above.
71
73
  sarj-iac-lint = { path = "../iac", editable = true }
@@ -63,6 +63,7 @@ from .libs.linting.library_policy import (
63
63
  )
64
64
  from .libs.linting.policy import Policy
65
65
  from .libs.linting.runner import group_paths, run as check
66
+ from .libs.linting.scheduling import analyze_groups
66
67
  from .libs.rules import RuleEngine, RuleId, RuleSelection, RuleSelector
67
68
  from .libs.typed_containers import is_object_list, is_object_mapping
68
69
 
@@ -238,7 +239,7 @@ class Standards:
238
239
  return _operation_result(max(source_status, eslint_status, 1 if findings else 0), findings=findings)
239
240
  return _operation_result(_verify(self.root))
240
241
 
241
- def analyze( # ruff: ignore[too-many-locals] -- one boundary coordinates routing, policy, and coverage.
242
+ def analyze(
242
243
  self,
243
244
  paths: Sequence[str] | None = None,
244
245
  *,
@@ -250,7 +251,10 @@ class Standards:
250
251
  react_doctor_triggered: bool = False,
251
252
  include_react_doctor: bool = True,
252
253
  pass_on_unpruned_eslint_suppressions: bool = False,
254
+ jobs: int = 1,
253
255
  ) -> AnalysisReport:
256
+ if jobs not in {1, 2}:
257
+ return _failed_analysis(self.root, "invalid-input", "analysis jobs must be 1 or 2")
254
258
  try:
255
259
  normalized_trust = TrustMode(trust)
256
260
  normalized_mode = AnalysisMode(mode)
@@ -277,19 +281,24 @@ class Standards:
277
281
  react_doctor_full_scan = not staged and (
278
282
  requested_paths is None or any(Path(path) == Path() for path in requested_paths)
279
283
  )
280
- native = analyze_paths(
281
- active_selected,
282
- root=self.root,
283
- policy=selection_policy,
284
- grouped=selected_groups,
285
- rule_selection=rule_selection,
286
- )
287
- if rule_selection is None:
288
- native = _with_library_policy(self.root, native, active_selected, selection_policy)
289
- if rule_selection is None and normalized_mode is AnalysisMode.POLICY:
290
- native = _with_repository_analysis(self.root, native, staged=staged)
284
+
285
+ def native_analysis() -> tuple[ToolReport, ...]:
286
+ native = analyze_paths(
287
+ active_selected,
288
+ root=self.root,
289
+ policy=selection_policy,
290
+ grouped=selected_groups,
291
+ rule_selection=rule_selection,
292
+ )
293
+ if rule_selection is None:
294
+ native = _with_library_policy(self.root, native, active_selected, selection_policy)
295
+ if rule_selection is None and normalized_mode is AnalysisMode.POLICY:
296
+ native = _with_repository_analysis(self.root, native, staged=staged)
297
+ return native.tools
298
+
291
299
  coverage = _selection_coverage(self.root, selected, active_selected, selected_groups, rule_selection)
292
300
  if not external:
301
+ native = report_from_tools(self.root, native_analysis())
293
302
  _native_typescript_coverage(selected_groups, adopted, rule_selection, coverage)
294
303
  if normalized_mode in {AnalysisMode.POLICY, AnalysisMode.OBSERVE}:
295
304
  native = _with_warning_severity(native, _warning_rule_keys())
@@ -305,21 +314,26 @@ class Standards:
305
314
  CoverageDisposition.NOT_REQUESTED,
306
315
  )
307
316
  )
308
- external_reports = _selected_external_analysis(
309
- active_selected,
310
- root=self.root,
311
- selected_groups=selected_groups,
312
- adopted=adopted,
313
- selection_policy=selection_policy,
314
- rule_selection=rule_selection,
315
- normalized_trust=normalized_trust,
316
- include_react_doctor=include_react_doctor,
317
- react_doctor_triggered=react_doctor_triggered,
318
- staged=staged,
319
- react_doctor_full_scan=react_doctor_full_scan,
320
- pass_on_unpruned_eslint_suppressions=pass_on_unpruned_eslint_suppressions,
317
+
318
+ def external_analysis() -> tuple[ToolReport, ...]:
319
+ return _selected_external_analysis(
320
+ active_selected,
321
+ root=self.root,
322
+ selected_groups=selected_groups,
323
+ adopted=adopted,
324
+ selection_policy=selection_policy,
325
+ rule_selection=rule_selection,
326
+ normalized_trust=normalized_trust,
327
+ include_react_doctor=include_react_doctor,
328
+ react_doctor_triggered=react_doctor_triggered,
329
+ staged=staged,
330
+ react_doctor_full_scan=react_doctor_full_scan,
331
+ pass_on_unpruned_eslint_suppressions=pass_on_unpruned_eslint_suppressions,
332
+ )
333
+
334
+ combined = report_from_tools(
335
+ self.root, analyze_groups(self.root, native_analysis, external_analysis, jobs=jobs)
321
336
  )
322
- combined = report_from_tools(self.root, (*native.tools, *external_reports))
323
337
  if normalized_mode in {AnalysisMode.POLICY, AnalysisMode.OBSERVE}:
324
338
  combined = _with_warning_severity(combined, _warning_rule_keys())
325
339
  return _with_coverage(
@@ -335,8 +349,9 @@ class Standards:
335
349
  mode: AnalysisMode | str = AnalysisMode.POLICY,
336
350
  rules: Sequence[str | RuleSelector] | None = None,
337
351
  staged: bool = False,
352
+ jobs: int = 1,
338
353
  ) -> AnalysisReport:
339
- return self.analyze(paths, external=external, trust=trust, mode=mode, rules=rules, staged=staged)
354
+ return self.analyze(paths, external=external, trust=trust, mode=mode, rules=rules, staged=staged, jobs=jobs)
340
355
 
341
356
  def fix(self) -> Result:
342
357
  from .libs.adoption import ( # ruff: ignore[import-outside-top-level] -- selected operation only
@@ -603,6 +618,11 @@ def _selected_external_analysis(
603
618
  react_doctor_full_scan: bool,
604
619
  pass_on_unpruned_eslint_suppressions: bool,
605
620
  ) -> tuple[ToolReport, ...]:
621
+ rule_ids = (
622
+ frozenset(str(value) for value in rule_selection.ids_for(RuleEngine.ESLINT))
623
+ if rule_selection is not None
624
+ else None
625
+ )
606
626
  run_eslint = rule_selection is None or RuleEngine.ESLINT in rule_selection.engines
607
627
  external_reports = (
608
628
  (
@@ -615,6 +635,7 @@ def _selected_external_analysis(
615
635
  frozenset({"eslint"}) if rule_selection is not None else frozenset(adopted.enabled_capabilities)
616
636
  ),
617
637
  grouped=selected_groups,
638
+ rule_ids=rule_ids,
618
639
  include_react_doctor=include_react_doctor and rule_selection is None,
619
640
  force_react_doctor=react_doctor_triggered,
620
641
  react_doctor_staged=staged,
@@ -628,6 +649,7 @@ def _selected_external_analysis(
628
649
  trust=normalized_trust,
629
650
  capabilities=frozenset({"eslint"}) if rule_selection is not None else None,
630
651
  grouped=selected_groups,
652
+ rule_ids=rule_ids,
631
653
  include_react_doctor=include_react_doctor and rule_selection is None,
632
654
  force_react_doctor=react_doctor_triggered,
633
655
  react_doctor_staged=staged,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass, field
3
+ from dataclasses import asdict, dataclass, field
4
4
  from datetime import timedelta
5
5
  from enum import StrEnum
6
6
  from functools import lru_cache
@@ -122,6 +122,7 @@ class _Args:
122
122
  roots: list[Path] = field(default_factory=list)
123
123
  include_text: Path | None = None
124
124
  rules_cmd: str = ""
125
+ repeats: int = 5
125
126
  rule_category: str = ""
126
127
  rule_summary: str = ""
127
128
  apply_rule: bool = False
@@ -145,6 +146,7 @@ class _Args:
145
146
  release_exclude_file: list[Path] = field(default_factory=list)
146
147
  output: Path | None = None
147
148
  external: bool = False
149
+ jobs: int = 1
148
150
  trust: str = "safe"
149
151
  trust_repository_code: bool = False
150
152
  before: str = ""
@@ -156,6 +158,7 @@ class _Args:
156
158
  hooks: manifest.HookManager | None = None
157
159
  show_cmd: str = ""
158
160
  catalog_cmd: str = ""
161
+ typescript_built: bool = False
159
162
  exclude_cmd: str = ""
160
163
  exclude_kind: str = ""
161
164
  value: str = ""
@@ -1000,6 +1003,7 @@ def cmd_verify(args: _Args) -> int:
1000
1003
  root,
1001
1004
  None if adopted is None else adopted.verify_paths,
1002
1005
  raw=adopted is None,
1006
+ jobs=args.jobs,
1003
1007
  trusted=args.trust_repository_code,
1004
1008
  )
1005
1009
 
@@ -1076,7 +1080,7 @@ def cmd_check(args: _Args) -> int:
1076
1080
  return health_status
1077
1081
  args.files = [path for path in args.files if runner.accepts_hook_path(Path(path), root=root)]
1078
1082
  if not args.files and not scope.react_doctor_triggered:
1079
- return _run_canonical_check(root, (), trusted=args.trust_repository_code, staged=True)
1083
+ return _run_canonical_check(root, (), trusted=args.trust_repository_code, staged=True, jobs=args.jobs)
1080
1084
  if args.output_format != "text":
1081
1085
  return _check_machine_output(args, root, scope)
1082
1086
  return _check_text_output(args, root, scope)
@@ -1090,6 +1094,7 @@ def _check_selected_rules(args: _Args, root: Path, scope: _CheckScope) -> int:
1090
1094
  paths = args.files if args.files or args.staged or scope.pull_request_scoped else None
1091
1095
  report = Standards(root).analyze(
1092
1096
  paths,
1097
+ jobs=args.jobs,
1093
1098
  rules=args.selected_rules,
1094
1099
  external=any(selector.engine.value == "eslint" for selector in args.selected_rules),
1095
1100
  trust=TrustMode.TRUSTED if args.trust_repository_code else TrustMode.SAFE,
@@ -1110,8 +1115,9 @@ def _check_text_output(args: _Args, root: Path, scope: _CheckScope) -> int:
1110
1115
  (),
1111
1116
  trusted=args.trust_repository_code,
1112
1117
  react_doctor_triggered=True,
1118
+ jobs=args.jobs,
1113
1119
  )
1114
- return _run_canonical_check(root, (), trusted=args.trust_repository_code)
1120
+ return _run_canonical_check(root, (), trusted=args.trust_repository_code, jobs=args.jobs)
1115
1121
  if not args.files:
1116
1122
  return cmd_verify(args)
1117
1123
  check_options: dict[str, bool] = {
@@ -1120,7 +1126,7 @@ def _check_text_output(args: _Args, root: Path, scope: _CheckScope) -> int:
1120
1126
  }
1121
1127
  if scope.react_doctor_triggered:
1122
1128
  check_options["react_doctor_triggered"] = True
1123
- return _run_canonical_check(root, list(args.files), **check_options)
1129
+ return _run_canonical_check(root, list(args.files), jobs=args.jobs, **check_options)
1124
1130
 
1125
1131
 
1126
1132
  @dataclass(slots=True)
@@ -1229,6 +1235,7 @@ def _check_machine_output(args: _Args, root: Path, scope: _CheckScope) -> int:
1229
1235
  report = Standards(root).analyze(
1230
1236
  (),
1231
1237
  external=True,
1238
+ jobs=args.jobs,
1232
1239
  trust=TrustMode.TRUSTED if args.trust_repository_code else TrustMode.SAFE,
1233
1240
  mode=AnalysisMode.POLICY,
1234
1241
  react_doctor_triggered=scope.react_doctor_triggered,
@@ -1308,6 +1315,7 @@ def _run_canonical_check(
1308
1315
  trusted: bool = False,
1309
1316
  staged: bool = False,
1310
1317
  react_doctor_triggered: bool = False,
1318
+ jobs: int = 1,
1311
1319
  ) -> int:
1312
1320
  from sarj_standards.api import AnalysisMode, Standards, TrustMode # ruff: ignore[import-outside-top-level]
1313
1321
  from sarj_standards.libs.diagnostics import to_text # ruff: ignore[import-outside-top-level]
@@ -1315,6 +1323,7 @@ def _run_canonical_check(
1315
1323
  report = Standards(root).analyze(
1316
1324
  paths,
1317
1325
  external=True,
1326
+ jobs=jobs,
1318
1327
  trust=TrustMode.TRUSTED if trusted else TrustMode.SAFE,
1319
1328
  mode=AnalysisMode.RAW if raw else AnalysisMode.POLICY,
1320
1329
  staged=staged,
@@ -1337,6 +1346,7 @@ def cmd_analyze(args: _Args) -> int:
1337
1346
  return 2
1338
1347
  report = Standards(root).analyze(
1339
1348
  args.files or None,
1349
+ jobs=args.jobs,
1340
1350
  external=args.external,
1341
1351
  trust=args.trust,
1342
1352
  mode=AnalysisMode(args.analysis_mode),
@@ -1359,6 +1369,7 @@ def cmd_rule_evaluate(args: _Args) -> int:
1359
1369
  return 2
1360
1370
  report = Standards(root).analyze(
1361
1371
  args.files or None,
1372
+ jobs=args.jobs,
1362
1373
  external=any(selector.engine.value == "eslint" for selector in args.selected_rules),
1363
1374
  trust=TrustMode.TRUSTED if args.trust_repository_code else TrustMode.SAFE,
1364
1375
  mode=(AnalysisMode.CORPUS if args.evaluation_scope is _EvaluationScope.CORPUS else AnalysisMode.POLICY),
@@ -1548,6 +1559,7 @@ def cmd_observe(args: _Args) -> int:
1548
1559
  return 2
1549
1560
  report = Standards(root).analyze(
1550
1561
  args.files or None,
1562
+ jobs=args.jobs,
1551
1563
  external=any(selector.engine.value == "eslint" for selector in args.selected_rules),
1552
1564
  trust=TrustMode.TRUSTED if args.trust_repository_code else TrustMode.SAFE,
1553
1565
  mode=AnalysisMode.OBSERVE,
@@ -2621,6 +2633,7 @@ class _ExcludeKind(StrEnum):
2621
2633
 
2622
2634
 
2623
2635
  class _ReleaseTargetChoice(StrEnum):
2636
+ CONTRACTS = "contracts"
2624
2637
  TYPESCRIPT = "typescript"
2625
2638
  BOOTSTRAP = "bootstrap"
2626
2639
  PYTHON = "python"
@@ -2837,6 +2850,9 @@ def build_app(handler: Callable[[_Args], int] = _dispatch) -> typer.Typer:
2837
2850
  help="allow executable repository ESLint configuration (generated hooks and CI set this explicitly)",
2838
2851
  ),
2839
2852
  ] = False,
2853
+ jobs: Annotated[
2854
+ int, typer.Option("--jobs", min=1, max=2, help="overlap native and external analysis (default: 1)")
2855
+ ] = 1,
2840
2856
  staged: Annotated[
2841
2857
  bool,
2842
2858
  typer.Option(
@@ -2861,6 +2877,7 @@ def build_app(handler: Callable[[_Args], int] = _dispatch) -> typer.Typer:
2861
2877
  _Args(
2862
2878
  dest=_command_root(ctx),
2863
2879
  cmd="check",
2880
+ jobs=jobs,
2864
2881
  trust_repository_code=trust_repository_code,
2865
2882
  staged=staged,
2866
2883
  selected_rules=selected_rules if selected_rules is not None else [],
@@ -3725,6 +3742,31 @@ def build_app(handler: Callable[[_Args], int] = _dispatch) -> typer.Typer:
3725
3742
  )
3726
3743
  )
3727
3744
 
3745
+ @group_maintain_rules.command("bench", help="measure native Python analysis with diagnostic and source digests")
3746
+ def command_maintain_rules_bench(
3747
+ ctx: typer.Context,
3748
+ files: Annotated[list[str], typer.Argument(help="Python files or directories")],
3749
+ *,
3750
+ repeats: Annotated[int, typer.Option(min=1)] = 5,
3751
+ selected_rules: Annotated[
3752
+ list[RuleSelector] | None,
3753
+ typer.Option(
3754
+ "--rule", parser=_parse_rule_selector, help="python:ID selector; defaults to all Python rules"
3755
+ ),
3756
+ ] = None,
3757
+ ) -> int:
3758
+ return handler(
3759
+ _Args(
3760
+ dest=_command_root(ctx),
3761
+ cmd="maintain",
3762
+ repo_cmd="rules",
3763
+ rules_cmd="bench",
3764
+ files=files,
3765
+ repeats=repeats,
3766
+ selected_rules=selected_rules or [],
3767
+ )
3768
+ )
3769
+
3728
3770
  @group_maintain_rules.command("changes", help="compare rule inventory and policy between two Git revisions")
3729
3771
  def command_maintain_rules_changes(
3730
3772
  ctx: typer.Context,
@@ -3806,6 +3848,11 @@ def build_app(handler: Callable[[_Args], int] = _dispatch) -> typer.Typer:
3806
3848
  @group_maintain_catalog.command("check", help="verify the public catalog matches every live rule")
3807
3849
  def command_maintain_catalog_check(
3808
3850
  ctx: typer.Context,
3851
+ *,
3852
+ typescript_built: Annotated[
3853
+ bool,
3854
+ typer.Option("--typescript-built", help="reuse the TypeScript build completed by this verification run"),
3855
+ ] = False,
3809
3856
  ) -> int:
3810
3857
  return handler(
3811
3858
  _Args(
@@ -3813,6 +3860,7 @@ def build_app(handler: Callable[[_Args], int] = _dispatch) -> typer.Typer:
3813
3860
  cmd="maintain",
3814
3861
  repo_cmd="catalog",
3815
3862
  catalog_cmd="check",
3863
+ typescript_built=typescript_built,
3816
3864
  )
3817
3865
  )
3818
3866
 
@@ -3972,8 +4020,11 @@ def _run_repo_check(args: _Args) -> int:
3972
4020
 
3973
4021
 
3974
4022
  def _run_repo_sync_ledger(args: _Args) -> int:
3975
- from sarj_standards.libs.repository import rule_maintenance # ruff: ignore[import-outside-top-level]
4023
+ from sarj_standards.libs.repository import rule_checkout, rule_maintenance # ruff: ignore[import-outside-top-level]
3976
4024
 
4025
+ rule_checkout.ensure_current(
4026
+ _resolve_dest(args.dest), ("maintain", "sync-ledger", *(("--check",) if args.check else ())), all_engines=True
4027
+ )
3977
4028
  result = rule_maintenance.sync_ledger(_resolve_dest(args.dest), check=args.check)
3978
4029
  print(result.message)
3979
4030
  return result.status
@@ -4010,8 +4061,17 @@ def _run_repo_hooks(args: _Args) -> int:
4010
4061
 
4011
4062
 
4012
4063
  def _run_repo_rules(args: _Args) -> int:
4013
- from sarj_standards.libs.repository import rule_inventory_artifact # ruff: ignore[import-outside-top-level]
4064
+ from sarj_standards.libs.repository import rule_checkout, rule_inventory_artifact # ruff: ignore[import-outside-top-level]
4014
4065
 
4066
+ if args.selector is not None and args.rules_cmd in {"new", "verify", "prepare", "stage-warning", "promote-error"}:
4067
+ rule_checkout.ensure_current(
4068
+ _resolve_dest(args.dest),
4069
+ ("maintain", "rules", args.rules_cmd, str(args.selector)),
4070
+ engine=args.selector.engine,
4071
+ all_engines=args.rules_cmd in {"prepare", "stage-warning", "promote-error"},
4072
+ )
4073
+ if args.rules_cmd in {"sync", "check"}:
4074
+ rule_checkout.ensure_current(_resolve_dest(args.dest), ("maintain", "rules", args.rules_cmd), all_engines=True)
4015
4075
  if args.rules_cmd == "manifest":
4016
4076
  print(json.dumps(rule_inventory_artifact.load(), indent=2))
4017
4077
  return 0
@@ -4019,6 +4079,8 @@ def _run_repo_rules(args: _Args) -> int:
4019
4079
  return _run_repo_rules_changes(args)
4020
4080
  if args.rules_cmd == "evaluate":
4021
4081
  return cmd_rule_evaluate(args)
4082
+ if args.rules_cmd == "bench":
4083
+ return _run_repo_rules_bench(args)
4022
4084
  if args.rules_cmd == "new":
4023
4085
  return _run_repo_rules_new(args)
4024
4086
  if args.rules_cmd == "verify":
@@ -4030,10 +4092,38 @@ def _run_repo_rules(args: _Args) -> int:
4030
4092
  return result.status
4031
4093
 
4032
4094
 
4095
+ def _run_repo_rules_bench(args: _Args) -> int:
4096
+ from sarj_python_lint.benchmark import measure # ruff: ignore[import-outside-top-level] -- benchmark-only dependency
4097
+ from sarj_python_lint.rules import REGISTRY # ruff: ignore[import-outside-top-level] -- benchmark selection
4098
+
4099
+ for selector in args.selected_rules:
4100
+ if selector.engine.value != "python" or selector.rule_id not in REGISTRY:
4101
+ print(f"error: benchmark requires a registered python:ID selector: {selector}", file=sys.stderr)
4102
+ return 2
4103
+ root = _resolve_dest(args.dest)
4104
+ try:
4105
+ result = measure(
4106
+ [root / path for path in args.files],
4107
+ [selector.rule_id for selector in args.selected_rules] or None,
4108
+ repeats=args.repeats,
4109
+ )
4110
+ except (OSError, ValueError, RuntimeError) as exc:
4111
+ print(f"error: benchmark failed: {exc}", file=sys.stderr)
4112
+ return 2
4113
+ print(json.dumps(asdict(result), indent=2))
4114
+ return 0
4115
+
4116
+
4033
4117
  def _run_repo_catalog(args: _Args) -> int:
4034
- from sarj_standards.libs.repository import rule_catalog_artifact # ruff: ignore[import-outside-top-level]
4118
+ from sarj_standards.libs.repository import rule_catalog_artifact, rule_checkout # ruff: ignore[import-outside-top-level]
4035
4119
 
4036
- result = rule_catalog_artifact.sync(_resolve_dest(args.dest), check=args.catalog_cmd == "check")
4120
+ root = _resolve_dest(args.dest)
4121
+ rule_checkout.ensure_current(root, ("maintain", "catalog", args.catalog_cmd), all_engines=True)
4122
+ if args.typescript_built:
4123
+ projection = rule_catalog_artifact.typescript_specs(root, already_built=True)
4124
+ result = rule_catalog_artifact.sync(root, check=args.catalog_cmd == "check", typescript=projection)
4125
+ else:
4126
+ result = rule_catalog_artifact.sync(root, check=args.catalog_cmd == "check")
4037
4127
  print(result.message)
4038
4128
  return result.status
4039
4129
 
@@ -4150,7 +4240,7 @@ def _run_repo_release_publish(args: _Args, root: Path) -> int:
4150
4240
 
4151
4241
  target = args.release_target
4152
4242
  match target:
4153
- case "typescript" | "bootstrap" | "python" | "sql" | "iac" | "standards" | "tsconfig":
4243
+ case "typescript" | "bootstrap" | "contracts" | "python" | "sql" | "iac" | "standards" | "tsconfig":
4154
4244
  publish_target = target
4155
4245
  case _:
4156
4246
  return 2
@@ -4218,7 +4308,7 @@ def _run_repo_rules_verify(args: _Args) -> int:
4218
4308
  msg = "verify requires a rule selector"
4219
4309
  raise TypeError(msg)
4220
4310
  try:
4221
- result = rule_authoring.verify(_resolve_dest(args.dest), args.selector)
4311
+ result = rule_authoring.verify(_resolve_dest(args.dest), args.selector, run_tests=True)
4222
4312
  except (OSError, TypeError, ValueError, RuntimeError) as exc:
4223
4313
  print(f"error: cannot verify rule: {exc}", file=sys.stderr)
4224
4314
  return 2
@@ -4232,22 +4322,28 @@ def _run_repo_rules_stage_warning(args: _Args) -> int:
4232
4322
  if args.selector is None: # pragma: no cover - Typer requires the positional value
4233
4323
  msg = f"{args.rules_cmd} requires a rule selector"
4234
4324
  raise TypeError(msg)
4325
+ typescript = None
4235
4326
  if args.rules_cmd == "prepare":
4236
4327
  from sarj_standards.libs.repository import ( # ruff: ignore[import-outside-top-level]
4237
4328
  rule_authoring,
4238
4329
  )
4239
4330
 
4240
4331
  try:
4241
- verified = rule_authoring.verify(_resolve_dest(args.dest), args.selector)
4332
+ verified = rule_authoring.verify(_resolve_dest(args.dest), args.selector, run_tests=True)
4242
4333
  except (OSError, TypeError, ValueError, RuntimeError) as exc:
4243
4334
  print(f"error: cannot verify rule before preparation: {exc}", file=sys.stderr)
4244
4335
  return 2
4245
4336
  if verified.status != 0:
4246
4337
  print(verified.message)
4247
4338
  return verified.status
4339
+ typescript = verified.typescript
4248
4340
  try:
4249
4341
  operation = rule_lifecycle.promote_error if args.rules_cmd == "promote-error" else rule_lifecycle.stage_warning
4250
- result = operation(_resolve_dest(args.dest), args.selector, check=args.check)
4342
+ result = (
4343
+ operation(_resolve_dest(args.dest), args.selector, check=args.check)
4344
+ if typescript is None
4345
+ else operation(_resolve_dest(args.dest), args.selector, check=args.check, typescript=typescript)
4346
+ )
4251
4347
  except (OSError, TypeError, ValueError, RuntimeError) as exc:
4252
4348
  print(f"error: cannot update rule lifecycle: {exc}", file=sys.stderr)
4253
4349
  return 2