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,66 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, replace
4
+ from typing import TYPE_CHECKING, Literal
5
+
6
+ from . import manifest, transaction
7
+
8
+
9
+ if TYPE_CHECKING:
10
+ from pathlib import Path
11
+
12
+
13
+ ExclusionKind = Literal["path", "rule"]
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Change:
18
+ kind: ExclusionKind
19
+ value: str
20
+ added: bool
21
+ changed: bool
22
+
23
+
24
+ def read(root: Path) -> manifest.Manifest:
25
+ adopted = manifest.load(root.resolve())
26
+ if adopted is None:
27
+ msg = "repository is not adopted; run `code-standards setup` first"
28
+ raise ValueError(msg)
29
+ return adopted
30
+
31
+
32
+ def add(root: Path, kind: ExclusionKind, value: str) -> Change:
33
+ return _change(root, kind, value, add_value=True)
34
+
35
+
36
+ def remove(root: Path, kind: ExclusionKind, value: str) -> Change:
37
+ return _change(root, kind, value, add_value=False)
38
+
39
+
40
+ def _change(root: Path, kind: ExclusionKind, value: str, *, add_value: bool) -> Change:
41
+ resolved = root.resolve()
42
+ target = manifest.manifest_path(resolved)
43
+ transaction.validate_targets(resolved, (target,))
44
+ expected = target.read_bytes() if target.is_file() else None
45
+ adopted = read(resolved)
46
+ normalized = (
47
+ manifest.validate_excluded_path(resolved, value) if kind == "path" else manifest.validate_excluded_rule(value)
48
+ )
49
+ values = adopted.excluded_paths if kind == "path" else adopted.excluded_rules
50
+ present = normalized in values
51
+ changed = (add_value and not present) or (not add_value and present)
52
+ if not changed:
53
+ return Change(kind, normalized, add_value, changed=False)
54
+
55
+ updated_values = (*values, normalized) if add_value else tuple(item for item in values if item != normalized)
56
+ updated = (
57
+ replace(adopted, excluded_paths=updated_values)
58
+ if kind == "path"
59
+ else replace(adopted, excluded_rules=updated_values)
60
+ )
61
+ transaction.assert_expected(resolved, target, expected)
62
+ transaction.atomic_write_text(resolved, target, updated.render())
63
+ # Treat serialization as a trust boundary too: a successful mutation must
64
+ # always leave a schema-valid manifest behind.
65
+ _ = read(resolved)
66
+ return Change(kind, normalized, add_value, changed=True)
@@ -0,0 +1,423 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from pathlib import Path
5
+ import re
6
+ import shlex
7
+ from typing import TYPE_CHECKING, Final, NamedTuple
8
+
9
+ import yaml
10
+
11
+ from . import launcher, manifest
12
+
13
+
14
+ if TYPE_CHECKING:
15
+ from .manifest import HookManager
16
+
17
+
18
+ LEFTHOOK_NAMES: Final = ("lefthook.yml", "lefthook.yaml")
19
+ PRECOMMIT_NAMES: Final = (".pre-commit-config.yaml", ".pre-commit-config.yml")
20
+ PRECOMMIT_FILES_PATTERN: Final = (
21
+ r"(?i)(\.py|\.[cm]?[jt]s|\.[jt]sx|\.sql|\.tf|\.tfvars|\.hcl|\.ya?ml|\.toml|\.jsonc|\.mdx?|"
22
+ r"\.(?:bash|cfg|conf|env|ini|properties|sh|tftpl|zsh)|(?:^|/)\.env(?:\..*)?$|"
23
+ r"(?:^|/)requirements(?:/.*|[^/]*\.(?:txt|in))$|"
24
+ r"(?:^|/)(?:Dockerfile(?:\..*)?|Gnumakefile|Justfile|Makefile|package\.json|pyrightconfig\.json))$"
25
+ )
26
+ _COMMANDS_BLOCK: Final = re.compile(r"(?m)^(?P<indent> +)commands:(?P<tail>[^\n]*)$")
27
+ _JOBS_BLOCK: Final = re.compile(r"(?m)^(?P<indent> +)jobs:(?P<tail>[^\n]*)$")
28
+ _MAX_JOB_DEPTH: Final = 64
29
+ _REPO_LINE: Final = re.compile(r"^(?P<indent> *)-\s+repo:\s*(?P<value>[^\r\n]+)(?:\r?\n)?$")
30
+ _OFFICIAL_STANDARDS_REPO: Final = re.compile(
31
+ r"(?i)(?:https?://github\.com/|ssh://git@github\.com/|git@github\.com:)"
32
+ r"sarj-ai/(?:code-)?standards(?:\.git)?/?"
33
+ )
34
+
35
+
36
+ class LefthookWrite(NamedTuple):
37
+ path: Path
38
+ contents: str
39
+
40
+
41
+ class PrecommitRepoBlock(NamedTuple):
42
+ start: int
43
+ end: int
44
+ indent: int
45
+ repository: str | None
46
+ text: str
47
+
48
+
49
+ class _PrecommitRepoSection(NamedTuple):
50
+ header: int
51
+ section_end: int
52
+ item_indent: int
53
+
54
+
55
+ class _LefthookEntries(NamedTuple):
56
+ text: str
57
+ layout: str
58
+ entries: Mapping[str, object]
59
+
60
+
61
+ class _LocatedBlock(NamedTuple):
62
+ match: re.Match[str]
63
+ section_end: int
64
+
65
+
66
+ def precommit_repo_blocks(text: str) -> tuple[PrecommitRepoBlock, ...]:
67
+ lines = text.splitlines(keepends=True)
68
+ section = _precommit_repo_section(lines)
69
+ if section is None:
70
+ return ()
71
+ header, section_end, item_indent = section
72
+ offsets = _line_offsets(lines)
73
+ blocks: list[PrecommitRepoBlock] = []
74
+ for index in range(header + 1, section_end):
75
+ line = lines[index]
76
+ match = _REPO_LINE.match(line)
77
+ if match is None or len(match.group("indent")) != item_indent:
78
+ continue
79
+ end_index = yaml_list_item_end(lines, index, item_indent)
80
+ start = offsets[index]
81
+ end = offsets[end_index] if end_index < len(offsets) else len(text)
82
+ repository = _repository_scalar(match.group("value"))
83
+ blocks.append(PrecommitRepoBlock(start, end, item_indent, repository, text[start:end]))
84
+ return tuple(blocks)
85
+
86
+
87
+ def _line_offsets(lines: list[str]) -> list[int]:
88
+ offsets: list[int] = []
89
+ offset = 0
90
+ for line in lines:
91
+ offsets.append(offset)
92
+ offset += len(line)
93
+ return offsets
94
+
95
+
96
+ def _repository_scalar(raw: str) -> str | None:
97
+ try:
98
+ parsed: object = yaml.safe_load(f"value: {raw}\n") # pyright: ignore[reportAny] -- narrowed below.
99
+ value = manifest.as_table(parsed).get("value")
100
+ except yaml.YAMLError:
101
+ return None
102
+ return value if isinstance(value, str) else None
103
+
104
+
105
+ def _precommit_repo_section(lines: list[str]) -> _PrecommitRepoSection | None:
106
+ header = next((index for index, line in enumerate(lines) if re.match(r"^repos:\s*(?:#.*)?(?:\r?\n)?$", line)), None)
107
+ if header is None:
108
+ return None
109
+ section_end = len(lines)
110
+ for index in range(header + 1, len(lines)):
111
+ stripped = lines[index].strip()
112
+ repo_item = _REPO_LINE.match(lines[index]) is not None
113
+ if (
114
+ stripped
115
+ and not stripped.startswith("#")
116
+ and not repo_item
117
+ and len(lines[index]) == len(lines[index].lstrip(" "))
118
+ ):
119
+ section_end = index
120
+ break
121
+ item_indent = next(
122
+ (
123
+ len(match.group("indent"))
124
+ for line in lines[header + 1 : section_end]
125
+ if (match := _REPO_LINE.match(line)) is not None
126
+ ),
127
+ None,
128
+ )
129
+ return None if item_indent is None else _PrecommitRepoSection(header, section_end, item_indent)
130
+
131
+
132
+ def yaml_list_item_end(lines: list[str], start: int, indent: int) -> int:
133
+ trailing: int | None = None
134
+ index = start + 1
135
+ while index < len(lines):
136
+ stripped = lines[index].lstrip(" ")
137
+ if not stripped.strip() or stripped.startswith("#"):
138
+ trailing = index if trailing is None else trailing
139
+ index += 1
140
+ continue
141
+ candidate_indent = len(lines[index]) - len(stripped)
142
+ if candidate_indent <= indent:
143
+ return trailing if trailing is not None else index
144
+ trailing = None
145
+ index += 1
146
+ return trailing if trailing is not None else len(lines)
147
+
148
+
149
+ def is_official_standards_repo(repository: str | None) -> bool:
150
+ return repository is not None and _OFFICIAL_STANDARDS_REPO.fullmatch(repository.strip()) is not None
151
+
152
+
153
+ def lefthook_config(root: Path) -> Path | None:
154
+ return next((root / name for name in LEFTHOOK_NAMES if (root / name).is_file()), None)
155
+
156
+
157
+ def detect_manager(root: Path) -> HookManager:
158
+ return "lefthook" if lefthook_config(root) is not None else "pre-commit"
159
+
160
+
161
+ def precommit_runs_staged_check(root: Path) -> bool:
162
+ paths = [root / name for name in PRECOMMIT_NAMES if (root / name).is_file()]
163
+ if len(paths) != 1:
164
+ return False
165
+ try:
166
+ parsed: object = yaml.safe_load( # pyright: ignore[reportAny] -- narrowed below.
167
+ paths[0].read_text(encoding="utf-8")
168
+ )
169
+ except OSError, UnicodeError, yaml.YAMLError:
170
+ return False
171
+ candidates: list[dict[str, object]] = []
172
+ for raw_repository in manifest.list_field(manifest.as_table(parsed), "repos"):
173
+ repository = manifest.as_table(raw_repository)
174
+ if repository.get("repo") != "local":
175
+ continue
176
+ candidates.extend(
177
+ hook
178
+ for raw_hook in manifest.list_field(repository, "hooks")
179
+ if (hook := manifest.as_table(raw_hook)).get("id") in {"sarj-standards-check", "sarj-standards-drift"}
180
+ )
181
+ if len(candidates) != 1:
182
+ return False
183
+ candidate = candidates[0]
184
+ return (
185
+ candidate.get("id") == "sarj-standards-check"
186
+ and _runs_staged_check(candidate.get("entry"))
187
+ and candidate.get("language") == "system"
188
+ and "verbose" not in candidate
189
+ and candidate.get("always_run") is True
190
+ and candidate.get("pass_filenames") is True
191
+ and candidate.get("require_serial") is True
192
+ and candidate.get("files") == PRECOMMIT_FILES_PATTERN
193
+ and candidate.get("stages") == ["pre-commit"]
194
+ )
195
+
196
+
197
+ def lefthook_runs_staged_check(root: Path) -> bool:
198
+ path = lefthook_config(root)
199
+ if path is None:
200
+ return False
201
+ try:
202
+ parsed: object = yaml.safe_load( # pyright: ignore[reportAny] -- narrow the untyped YAML parser boundary below.
203
+ path.read_text(encoding="utf-8")
204
+ )
205
+ except OSError, UnicodeError, yaml.YAMLError:
206
+ return False
207
+ document = manifest.as_table(parsed)
208
+ pre_commit = manifest.as_table(document.get("pre-commit"))
209
+ values = tuple(_lefthook_run_values(pre_commit))
210
+ return values.count(_canonical_lefthook_command(root)) == 1
211
+
212
+
213
+ def wire_lefthook_staged_check(root: Path) -> LefthookWrite:
214
+ path = lefthook_config(root)
215
+ if path is None:
216
+ msg = "--hooks lefthook requires lefthook.yml or lefthook.yaml"
217
+ raise ValueError(msg)
218
+ text, layout, entries = _load_lefthook_entries(path)
219
+ staged_values = tuple(value for value in _lefthook_run_values_from_entries(entries) if _runs_staged_check(value))
220
+ if len(staged_values) > 1:
221
+ msg = f"cannot safely wire {path.name}: multiple staged Standards commands are active"
222
+ raise ValueError(msg)
223
+ if staged_values:
224
+ repaired = _replace_lefthook_run(
225
+ text,
226
+ old=staged_values[0],
227
+ new=_canonical_lefthook_command(root),
228
+ )
229
+ if repaired is None:
230
+ msg = f"cannot safely wire {path.name}: staged Standards command is not a scalar run value"
231
+ raise ValueError(msg)
232
+ return LefthookWrite(path, repaired)
233
+ block_match, section_end = _locate_block(path, text, layout)
234
+ name = "sarj-standards" if "sarj-standards" not in entries else "sarj-standards-staged"
235
+ if layout == "commands":
236
+ contents = _insert_staged_command(path, text, block_match, section_end, name)
237
+ else:
238
+ contents = _insert_staged_job(path, text, block_match, section_end, name)
239
+ return LefthookWrite(path, contents)
240
+
241
+
242
+ def _load_lefthook_entries(path: Path) -> _LefthookEntries:
243
+ try:
244
+ text = path.read_text(encoding="utf-8")
245
+ parsed: object = yaml.safe_load(text) # pyright: ignore[reportAny] -- narrowed immediately below.
246
+ document = manifest.as_table(parsed)
247
+ pre_commit = manifest.as_table(document.get("pre-commit"))
248
+ except (OSError, UnicodeError, TypeError, yaml.YAMLError) as exc:
249
+ msg = f"cannot safely wire {path.name}: expected valid pre-commit commands or jobs"
250
+ raise ValueError(msg) from exc
251
+ if "commands" in pre_commit:
252
+ return _LefthookEntries(text, "commands", manifest.as_table(pre_commit.get("commands")))
253
+ jobs = manifest.list_field(pre_commit, "jobs")
254
+ if "jobs" in pre_commit:
255
+ names = {
256
+ str(job.get("name")): job for value in jobs if (job := manifest.as_table(value)).get("name") is not None
257
+ }
258
+ return _LefthookEntries(text, "jobs", names)
259
+ msg = f"cannot safely wire {path.name}: expected block-style pre-commit commands or jobs"
260
+ raise ValueError(msg)
261
+
262
+
263
+ def _locate_block(path: Path, text: str, layout: str) -> _LocatedBlock:
264
+ pre_commit_match = re.search(r"(?m)^pre-commit:\s*(?:#.*)?$", text)
265
+ if pre_commit_match is None:
266
+ msg = f"cannot safely wire {path.name}: expected a block-style pre-commit.commands mapping"
267
+ raise ValueError(msg)
268
+ section_end_match = re.search(r"(?m)^[^\s#][^:]*:\s*", text[pre_commit_match.end() :])
269
+ section_end = len(text) if section_end_match is None else pre_commit_match.end() + section_end_match.start()
270
+ pattern = _COMMANDS_BLOCK if layout == "commands" else _JOBS_BLOCK
271
+ block_match = pattern.search(text, pre_commit_match.end(), section_end)
272
+ if block_match is None:
273
+ msg = f"cannot safely wire {path.name}: expected block-style pre-commit {layout}"
274
+ raise ValueError(msg)
275
+ return _LocatedBlock(block_match, section_end)
276
+
277
+
278
+ def _insert_staged_job(
279
+ path: Path,
280
+ text: str,
281
+ jobs_match: re.Match[str],
282
+ section_end: int,
283
+ name: str,
284
+ ) -> str:
285
+ block_indent = len(jobs_match.group("indent"))
286
+ child_indent = " " * (block_indent + 2)
287
+ rendered = f"{child_indent}- name: {name}\n{child_indent} run: {_canonical_lefthook_command(path.parent)}\n"
288
+ tail = jobs_match.group("tail")
289
+ if tail.strip():
290
+ msg = f"cannot safely wire {path.name}: expected block-style pre-commit jobs"
291
+ raise ValueError(msg)
292
+ insertion = jobs_match.end() + (1 if text[jobs_match.end() :].startswith("\n") else 0)
293
+ for line in text[insertion:section_end].splitlines(keepends=True):
294
+ stripped = line.strip()
295
+ if stripped and not stripped.startswith("#") and len(line) - len(line.lstrip(" ")) <= block_indent:
296
+ break
297
+ insertion += len(line)
298
+ separator = "" if insertion > jobs_match.end() else "\n"
299
+ return "".join((text[:insertion], separator, rendered, text[insertion:]))
300
+
301
+
302
+ def _insert_staged_command(
303
+ path: Path,
304
+ text: str,
305
+ commands_match: re.Match[str],
306
+ section_end: int,
307
+ name: str,
308
+ ) -> str:
309
+ command_indent = len(commands_match.group("indent"))
310
+ child_indent = " " * (command_indent + 2)
311
+ rendered = f"{child_indent}{name}:\n{child_indent} run: {_canonical_lefthook_command(path.parent)}\n"
312
+ absolute_start = commands_match.start()
313
+ absolute_end = commands_match.end()
314
+ tail = commands_match.group("tail")
315
+ if re.fullmatch(r"\s*\{\}\s*(?:#.*)?", tail):
316
+ comment = tail[tail.find("#") :] if "#" in tail else ""
317
+ replacement = f"{commands_match.group('indent')}commands:"
318
+ if comment:
319
+ replacement = f"{replacement} {comment}"
320
+ return "".join((text[:absolute_start], replacement, "\n", rendered, text[absolute_end + 1 :]))
321
+ if tail.strip():
322
+ msg = f"cannot safely wire {path.name}: expected a block-style pre-commit.commands mapping"
323
+ raise ValueError(msg)
324
+
325
+ insertion = absolute_end + (1 if text[absolute_end:].startswith("\n") else 0)
326
+ for line in text[insertion:section_end].splitlines(keepends=True):
327
+ stripped = line.strip()
328
+ if stripped and not stripped.startswith("#") and len(line) - len(line.lstrip(" ")) <= command_indent:
329
+ break
330
+ insertion += len(line)
331
+ separator = "" if insertion > absolute_end else "\n"
332
+ return "".join((text[:insertion], separator, rendered, text[insertion:]))
333
+
334
+
335
+ def _runs_staged_check(value: object) -> bool:
336
+ if not isinstance(value, str) or re.search(r"(?:&&|\|\||[;|`]|\$\()", value):
337
+ return False
338
+ migrated, count = launcher.rewrite_legacy_repository_invocations(value)
339
+ if count:
340
+ value = migrated
341
+ try:
342
+ tokens = shlex.split(value)
343
+ except ValueError:
344
+ return False
345
+ prefix = launcher.repository_argv()
346
+ return tuple(tokens[: len(prefix)]) == prefix and tokens[len(prefix) : len(prefix) + 2] == ["check", "--staged"]
347
+
348
+
349
+ def _canonical_lefthook_command(root: Path | None = None) -> str:
350
+ if root is not None and (root / "packages" / "standards" / "pyproject.toml").is_file():
351
+ return (
352
+ "uv run --project packages/standards --frozen code-standards check --staged "
353
+ "--trust-repository-code -- {staged_files}"
354
+ )
355
+ return f"{launcher.repository_command()} check --staged --trust-repository-code -- {{staged_files}}"
356
+
357
+
358
+ def _lefthook_run_values(value: object, *, depth: int = 0, seen: set[int] | None = None) -> list[str]:
359
+ if depth > _MAX_JOB_DEPTH:
360
+ return []
361
+ visited: set[int] = set() if seen is None else seen
362
+ if isinstance(value, (dict, list)):
363
+ identity = id(value) # pyright: ignore[reportUnknownArgumentType] -- identity is the cycle guard.
364
+ if identity in visited:
365
+ return []
366
+ visited.add(identity)
367
+ table = manifest.as_table(value) # pyright: ignore[reportUnknownArgumentType] -- narrowed parser value.
368
+ if table:
369
+ found: list[str] = [run for run in (table.get("run"),) if isinstance(run, str)]
370
+ for child in table.values():
371
+ found.extend(_lefthook_run_values(child, depth=depth + 1, seen=visited))
372
+ return found
373
+ if isinstance(value, list):
374
+ found = []
375
+ for child in manifest.list_field(
376
+ {"items": value}, # pyright: ignore[reportUnknownArgumentType] -- narrowed parser list.
377
+ "items",
378
+ ):
379
+ found.extend(_lefthook_run_values(child, depth=depth + 1, seen=visited))
380
+ return found
381
+ return []
382
+
383
+
384
+ def _lefthook_run_values_from_entries(entries: Mapping[str, object]) -> list[str]:
385
+ return _lefthook_run_values(entries)
386
+
387
+
388
+ def _replace_lefthook_run(text: str, *, old: str, new: str) -> str | None:
389
+ matches: list[re.Match[str]] = []
390
+ for match in re.finditer(r"(?m)^(?P<prefix>\s*run:\s*)(?P<value>[^\r\n]+)$", text):
391
+ try:
392
+ parsed: object = yaml.safe_load(f"value: {match.group('value')}\n") # pyright: ignore[reportAny]
393
+ value = manifest.as_table(parsed).get("value")
394
+ except yaml.YAMLError:
395
+ continue
396
+ if value == old:
397
+ matches.append(match)
398
+ if len(matches) != 1:
399
+ return None
400
+ match = matches[0]
401
+ return f"{text[: match.start()]}{match.group('prefix')}{new}{text[match.end() :]}"
402
+
403
+
404
+ def _jobs_run_staged_check(value: object, seen: set[int] | None = None, *, depth: int = 0) -> bool:
405
+ if not isinstance(value, list):
406
+ return False
407
+ if depth > _MAX_JOB_DEPTH:
408
+ return False
409
+ visited: set[int] = set() if seen is None else seen
410
+ identity = id(value) # pyright: ignore[reportUnknownArgumentType] -- YAML list identity detects alias cycles.
411
+ if identity in visited:
412
+ return False
413
+ visited.add(identity)
414
+ holder: dict[str, object] = {"jobs": value}
415
+ jobs = manifest.list_field(holder, "jobs")
416
+ for raw_job in jobs:
417
+ job = manifest.as_table(raw_job)
418
+ if _runs_staged_check(job.get("run")):
419
+ return True
420
+ group = manifest.as_table(job.get("group"))
421
+ if _jobs_run_staged_check(group.get("jobs"), visited, depth=depth + 1):
422
+ return True
423
+ return False