code-standards 7.0.0__py3-none-any.whl

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 (99) hide show
  1. code_standards-7.0.0.dist-info/METADATA +53 -0
  2. code_standards-7.0.0.dist-info/RECORD +99 -0
  3. code_standards-7.0.0.dist-info/WHEEL +4 -0
  4. code_standards-7.0.0.dist-info/entry_points.txt +3 -0
  5. code_standards-7.0.0.dist-info/licenses/LICENSE +21 -0
  6. sarj_standards/__init__.py +30 -0
  7. sarj_standards/__main__.py +5 -0
  8. sarj_standards/_meta.py +22 -0
  9. sarj_standards/api.py +890 -0
  10. sarj_standards/cli/__init__.py +0 -0
  11. sarj_standards/cli/main.py +2466 -0
  12. sarj_standards/configs/cli-reference.v1.json +1 -0
  13. sarj_standards/configs/doctor.config.json +22 -0
  14. sarj_standards/configs/eslint.application.mjs +1366 -0
  15. sarj_standards/configs/eslint.peers.json +44 -0
  16. sarj_standards/configs/eslint.strict.mjs +1060 -0
  17. sarj_standards/configs/markdownlint.strict.yaml +12 -0
  18. sarj_standards/configs/pyright.strict.json +96 -0
  19. sarj_standards/configs/ruff.application.toml +363 -0
  20. sarj_standards/configs/ruff.strict.toml +338 -0
  21. sarj_standards/configs/rule-inventory.v1.json +1 -0
  22. sarj_standards/configs/rule-ledger.json +846 -0
  23. sarj_standards/configs/rule-warning-levels.v1.json +1 -0
  24. sarj_standards/configs/taplo.strict.toml +14 -0
  25. sarj_standards/configs/yamllint.strict.yaml +25 -0
  26. sarj_standards/libs/__init__.py +0 -0
  27. sarj_standards/libs/adoption/__init__.py +0 -0
  28. sarj_standards/libs/adoption/configs.py +36 -0
  29. sarj_standards/libs/adoption/doctor.py +1346 -0
  30. sarj_standards/libs/adoption/exclusions.py +66 -0
  31. sarj_standards/libs/adoption/hooks.py +423 -0
  32. sarj_standards/libs/adoption/launcher.py +240 -0
  33. sarj_standards/libs/adoption/lifecycle.py +493 -0
  34. sarj_standards/libs/adoption/manifest.py +550 -0
  35. sarj_standards/libs/adoption/packagemanager.py +285 -0
  36. sarj_standards/libs/adoption/retired_suppressions.py +371 -0
  37. sarj_standards/libs/adoption/scaffold.py +1660 -0
  38. sarj_standards/libs/adoption/service.py +441 -0
  39. sarj_standards/libs/adoption/transaction.py +274 -0
  40. sarj_standards/libs/adoption/upgrade.py +516 -0
  41. sarj_standards/libs/adoption/uvtool.py +62 -0
  42. sarj_standards/libs/catalogs/__init__.py +9 -0
  43. sarj_standards/libs/catalogs/slack_automations.py +627 -0
  44. sarj_standards/libs/corpus/__init__.py +25 -0
  45. sarj_standards/libs/corpus/manifest.py +211 -0
  46. sarj_standards/libs/corpus/snapshot.py +222 -0
  47. sarj_standards/libs/diagnostics/__init__.py +65 -0
  48. sarj_standards/libs/diagnostics/analysis.schema.json +161 -0
  49. sarj_standards/libs/diagnostics/baseline.py +131 -0
  50. sarj_standards/libs/diagnostics/models.py +574 -0
  51. sarj_standards/libs/diagnostics/serialize.py +290 -0
  52. sarj_standards/libs/diagnostics/source.py +172 -0
  53. sarj_standards/libs/filesystem.py +11 -0
  54. sarj_standards/libs/linting/__init__.py +0 -0
  55. sarj_standards/libs/linting/analysis.py +422 -0
  56. sarj_standards/libs/linting/external.py +1454 -0
  57. sarj_standards/libs/linting/library_policy.py +688 -0
  58. sarj_standards/libs/linting/policy.py +152 -0
  59. sarj_standards/libs/linting/runner.py +442 -0
  60. sarj_standards/libs/linting/textlint.py +1605 -0
  61. sarj_standards/libs/release/__init__.py +98 -0
  62. sarj_standards/libs/release/_values.py +24 -0
  63. sarj_standards/libs/release/artifacts.py +191 -0
  64. sarj_standards/libs/release/causality.py +80 -0
  65. sarj_standards/libs/release/changes.py +48 -0
  66. sarj_standards/libs/release/process.py +128 -0
  67. sarj_standards/libs/release/publish.py +85 -0
  68. sarj_standards/libs/release/registry.py +271 -0
  69. sarj_standards/libs/release/release_age.py +218 -0
  70. sarj_standards/libs/release/rollout.py +1163 -0
  71. sarj_standards/libs/release/tags.py +373 -0
  72. sarj_standards/libs/release/typescript.py +191 -0
  73. sarj_standards/libs/repository/__init__.py +0 -0
  74. sarj_standards/libs/repository/cli_reference_artifact.py +324 -0
  75. sarj_standards/libs/repository/comment_corpus.py +536 -0
  76. sarj_standards/libs/repository/config_generation.py +146 -0
  77. sarj_standards/libs/repository/docs.py +347 -0
  78. sarj_standards/libs/repository/hooks.py +118 -0
  79. sarj_standards/libs/repository/ledger.py +99 -0
  80. sarj_standards/libs/repository/repository.py +744 -0
  81. sarj_standards/libs/repository/rule_authoring.py +246 -0
  82. sarj_standards/libs/repository/rule_catalog_artifact.py +479 -0
  83. sarj_standards/libs/repository/rule_changes.py +318 -0
  84. sarj_standards/libs/repository/rule_inventory_artifact.py +142 -0
  85. sarj_standards/libs/repository/rule_lifecycle.py +167 -0
  86. sarj_standards/libs/repository/rule_maintenance.py +225 -0
  87. sarj_standards/libs/rules/__init__.py +74 -0
  88. sarj_standards/libs/rules/catalog.py +145 -0
  89. sarj_standards/libs/rules/contracts.py +382 -0
  90. sarj_standards/libs/rules/corpus_runner.py +365 -0
  91. sarj_standards/libs/rules/evaluation.py +177 -0
  92. sarj_standards/libs/setup/__init__.py +4 -0
  93. sarj_standards/libs/setup/repository.py +40 -0
  94. sarj_standards/py.typed +0 -0
  95. sarj_standards/schemas/__init__.py +4 -0
  96. sarj_standards/schemas/_paths.py +7 -0
  97. sarj_standards/schemas/rule-catalog.v1.json +1 -0
  98. sarj_standards/schemas/rule-catalog.v1.schema.json +112 -0
  99. sarj_standards/schemas/slack-automations.v1.schema.json +1751 -0
@@ -0,0 +1,324 @@
1
+ # argparse exposes no public parser-graph traversal API.
2
+ # pyright: reportPrivateUsage=false
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from dataclasses import dataclass
8
+ import json
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING, Final, Literal, TypedDict, TypeGuard
11
+
12
+ from sarj_standards._meta import CONFIGS_DIR, __version__
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Iterable
17
+
18
+
19
+ SCHEMA_VERSION: Final = 1
20
+ CLI_REFERENCE_PATH: Final = CONFIGS_DIR / "cli-reference.v1.json"
21
+ _REPOSITORY_REFERENCE_PATH: Final = Path("packages/standards/src/sarj_standards/configs/cli-reference.v1.json")
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class ReferenceSyncResult:
26
+ status: int
27
+ message: str
28
+
29
+
30
+ class ReferenceArgument(TypedDict):
31
+ kind: Literal["positional", "option"]
32
+ names: list[str]
33
+ metavar: str | None
34
+ summary: str
35
+ choices: list[str]
36
+ required: bool
37
+ repeatable: bool
38
+
39
+
40
+ class ReferenceCommand(TypedDict):
41
+ name: str
42
+ path: list[str]
43
+ usage: str
44
+ summary: str
45
+ options: list[ReferenceArgument]
46
+ commands: list[ReferenceCommand]
47
+
48
+
49
+ class ReferenceLauncher(TypedDict):
50
+ install: str
51
+ runLatest: str
52
+
53
+
54
+ class CliReference(TypedDict):
55
+ schemaVersion: int
56
+ version: str
57
+ program: str
58
+ summary: str
59
+ epilog: str | None
60
+ globalOptions: list[ReferenceArgument]
61
+ commands: list[ReferenceCommand]
62
+ launcher: ReferenceLauncher
63
+
64
+
65
+ def validate(value: object) -> CliReference:
66
+ if not _is_object(value) or frozenset(value) != frozenset(
67
+ {
68
+ "schemaVersion",
69
+ "version",
70
+ "program",
71
+ "summary",
72
+ "epilog",
73
+ "globalOptions",
74
+ "commands",
75
+ "launcher",
76
+ }
77
+ ):
78
+ msg = "CLI reference has an invalid top-level shape"
79
+ raise ValueError(msg)
80
+ if value["schemaVersion"] != SCHEMA_VERSION:
81
+ msg = f"unsupported CLI reference schemaVersion: {value['schemaVersion']!r}; expected {SCHEMA_VERSION}"
82
+ raise ValueError(msg)
83
+ if not isinstance(value["version"], str) or not value["version"]:
84
+ msg = "CLI reference version must be a non-empty string"
85
+ raise ValueError(msg)
86
+ if not isinstance(value["program"], str) or not value["program"]:
87
+ msg = "CLI reference program must be a non-empty string"
88
+ raise ValueError(msg)
89
+ if not isinstance(value["summary"], str):
90
+ msg = "CLI reference summary must be a string"
91
+ raise TypeError(msg)
92
+ epilog = value["epilog"]
93
+ if epilog is not None and not isinstance(epilog, str):
94
+ msg = "CLI reference epilog must be null or a string"
95
+ raise TypeError(msg)
96
+ global_options = value["globalOptions"]
97
+ commands = value["commands"]
98
+ launcher = value["launcher"]
99
+ if not _is_argument_list(global_options) or not _is_command_list(commands) or not _is_launcher(launcher):
100
+ msg = "CLI reference options and commands must have the documented shape"
101
+ raise TypeError(msg)
102
+ return {
103
+ "schemaVersion": SCHEMA_VERSION,
104
+ "version": value["version"],
105
+ "program": value["program"],
106
+ "summary": value["summary"],
107
+ "epilog": epilog,
108
+ "globalOptions": global_options,
109
+ "commands": commands,
110
+ "launcher": launcher,
111
+ }
112
+
113
+
114
+ def _is_object(value: object) -> TypeGuard[dict[str, object]]:
115
+ return isinstance(value, dict)
116
+
117
+
118
+ def _is_object_list(value: object) -> TypeGuard[list[object]]:
119
+ return isinstance(value, list)
120
+
121
+
122
+ def _is_argument_list(value: object) -> TypeGuard[list[ReferenceArgument]]:
123
+ return _is_object_list(value) and all(_is_argument(item) for item in value)
124
+
125
+
126
+ def _is_argument(value: object) -> TypeGuard[ReferenceArgument]:
127
+ if not _is_object(value) or frozenset(value) != frozenset(ReferenceArgument.__required_keys__):
128
+ return False
129
+ kind = value["kind"]
130
+ metavar = value["metavar"]
131
+ return (
132
+ kind in {"positional", "option"}
133
+ and _is_string_list(value["names"])
134
+ and (metavar is None or isinstance(metavar, str))
135
+ and isinstance(value["summary"], str)
136
+ and _is_string_list(value["choices"])
137
+ and isinstance(value["required"], bool)
138
+ and isinstance(value["repeatable"], bool)
139
+ )
140
+
141
+
142
+ def _is_string_list(value: object) -> TypeGuard[list[str]]:
143
+ return _is_object_list(value) and all(isinstance(item, str) for item in value)
144
+
145
+
146
+ def _is_command_list(value: object) -> TypeGuard[list[ReferenceCommand]]:
147
+ return _is_object_list(value) and all(_is_command(item) for item in value)
148
+
149
+
150
+ def _is_command(value: object) -> TypeGuard[ReferenceCommand]:
151
+ if not _is_object(value) or frozenset(value) != frozenset(ReferenceCommand.__required_keys__):
152
+ return False
153
+ return (
154
+ isinstance(value["name"], str)
155
+ and _is_string_list(value["path"])
156
+ and isinstance(value["usage"], str)
157
+ and isinstance(value["summary"], str)
158
+ and _is_argument_list(value["options"])
159
+ and _is_command_list(value["commands"])
160
+ )
161
+
162
+
163
+ def _is_launcher(value: object) -> TypeGuard[ReferenceLauncher]:
164
+ return (
165
+ _is_object(value)
166
+ and frozenset(value) == frozenset(ReferenceLauncher.__required_keys__)
167
+ and isinstance(value["install"], str)
168
+ and bool(value["install"])
169
+ and isinstance(value["runLatest"], str)
170
+ and bool(value["runLatest"])
171
+ )
172
+
173
+
174
+ def load(path: Path = CLI_REFERENCE_PATH) -> CliReference:
175
+ try:
176
+ payload: object = json.loads(path.read_text(encoding="utf-8")) # pyright: ignore[reportAny]
177
+ except (OSError, json.JSONDecodeError) as exc:
178
+ msg = f"cannot load shipped CLI reference {path}: {exc}"
179
+ raise ValueError(msg) from exc
180
+ return validate(payload)
181
+
182
+
183
+ def build(parser: argparse.ArgumentParser) -> CliReference:
184
+ from sarj_standards.libs.adoption import launcher # ruff: ignore[import-outside-top-level]
185
+
186
+ root = _command(
187
+ parser,
188
+ name=parser.prog,
189
+ path=(),
190
+ summary=parser.description or "",
191
+ )
192
+ return validate(
193
+ {
194
+ "schemaVersion": SCHEMA_VERSION,
195
+ "version": __version__,
196
+ "program": parser.prog,
197
+ "summary": parser.description or "",
198
+ "epilog": parser.epilog,
199
+ "globalOptions": root["options"],
200
+ "commands": root["commands"],
201
+ "launcher": {"install": launcher.install(), "runLatest": launcher.latest()},
202
+ }
203
+ )
204
+
205
+
206
+ def _command(
207
+ parser: argparse.ArgumentParser,
208
+ *,
209
+ name: str,
210
+ path: tuple[str, ...],
211
+ summary: str,
212
+ ) -> ReferenceCommand:
213
+ arguments: list[ReferenceArgument] = []
214
+ commands: list[ReferenceCommand] = []
215
+ for action in parser._actions: # ruff: ignore[private-member-access]
216
+ if _is_subparsers(action):
217
+ summaries = _subcommand_summaries(action)
218
+ for command_name, command_parser in action.choices.items():
219
+ commands.append(
220
+ _command(
221
+ command_parser,
222
+ name=command_name,
223
+ path=(*path, command_name),
224
+ summary=summaries.get(command_name, ""),
225
+ )
226
+ )
227
+ continue
228
+ arguments.append(_argument(action))
229
+ return {
230
+ "name": name,
231
+ "path": list(path),
232
+ "usage": _usage(parser.prog, arguments, commands),
233
+ "summary": summary,
234
+ "options": arguments,
235
+ "commands": commands,
236
+ }
237
+
238
+
239
+ def _is_subparsers(
240
+ action: argparse.Action,
241
+ ) -> TypeGuard[argparse._SubParsersAction[argparse.ArgumentParser]]:
242
+ return isinstance(action, argparse._SubParsersAction) # ruff: ignore[private-member-access]
243
+
244
+
245
+ def _subcommand_summaries(
246
+ action: argparse._SubParsersAction[argparse.ArgumentParser],
247
+ ) -> dict[str, str]:
248
+ return {
249
+ choice.dest: "" if choice.help in {None, argparse.SUPPRESS} else str(choice.help)
250
+ for choice in action._choices_actions # ruff: ignore[private-member-access]
251
+ }
252
+
253
+
254
+ def _argument(action: argparse.Action) -> ReferenceArgument:
255
+ choices: Iterable[object] | None = action.choices
256
+ positional = not action.option_strings
257
+ return {
258
+ "kind": "positional" if positional else "option",
259
+ "names": [action.dest] if positional else list(action.option_strings),
260
+ "metavar": _metavar(action),
261
+ "summary": "" if action.help in {None, argparse.SUPPRESS} else str(action.help),
262
+ "choices": [] if choices is None else [str(choice) for choice in choices],
263
+ "required": action.required,
264
+ "repeatable": action.nargs in {"+", "*"}
265
+ or type(action).__name__ in {"_AppendAction", "_AppendConstAction", "_CountAction"},
266
+ }
267
+
268
+
269
+ def _metavar(action: argparse.Action) -> str | None:
270
+ if action.nargs == 0:
271
+ return None
272
+ if isinstance(action.metavar, tuple):
273
+ return " ".join(str(item) for item in action.metavar)
274
+ if action.metavar is not None:
275
+ return str(action.metavar)
276
+ return action.dest if not action.option_strings else action.dest.upper()
277
+
278
+
279
+ def _usage(
280
+ program: str,
281
+ arguments: list[ReferenceArgument],
282
+ commands: list[ReferenceCommand],
283
+ ) -> str:
284
+ parts = [program]
285
+ for argument in arguments:
286
+ if not argument["names"]:
287
+ continue
288
+ token = argument["names"][0]
289
+ metavar = argument["metavar"]
290
+ if metavar is not None:
291
+ token = f"{token} {metavar}" if argument["kind"] == "option" else metavar
292
+ if argument["repeatable"]:
293
+ token = f"{token} ..."
294
+ if not argument["required"]:
295
+ token = f"[{token}]"
296
+ parts.append(token)
297
+ if commands:
298
+ parts.append("{" + ",".join(command["name"] for command in commands) + "}")
299
+ return " ".join(parts)
300
+
301
+
302
+ def render(parser: argparse.ArgumentParser) -> str:
303
+ return json.dumps(build(parser), ensure_ascii=False, separators=(",", ":"), sort_keys=True) + "\n"
304
+
305
+
306
+ def sync(root: Path, parser: argparse.ArgumentParser, *, check: bool) -> ReferenceSyncResult:
307
+ from sarj_standards.libs.adoption import transaction # ruff: ignore[import-outside-top-level]
308
+
309
+ destination = root.resolve() / _REPOSITORY_REFERENCE_PATH
310
+ expected = render(parser)
311
+ try:
312
+ current = destination.read_text(encoding="utf-8")
313
+ except FileNotFoundError:
314
+ current = ""
315
+ if current == expected:
316
+ return ReferenceSyncResult(0, "ok: cli-reference.v1.json matches the parser graph")
317
+ if check:
318
+ return ReferenceSyncResult(
319
+ 1,
320
+ "drift: cli-reference.v1.json differs from the parser graph; "
321
+ "run `code-standards maintain cli-reference sync`",
322
+ )
323
+ transaction.atomic_write_text(root.resolve(), destination, expected)
324
+ return ReferenceSyncResult(0, "updated: cli-reference.v1.json")