chipgraph 0.1.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 (252) hide show
  1. chipgraph/__init__.py +7 -0
  2. chipgraph/adapters/__init__.py +1 -0
  3. chipgraph/adapters/format/__init__.py +6 -0
  4. chipgraph/adapters/format/__main__.py +16 -0
  5. chipgraph/adapters/format/_yaml_lines.py +54 -0
  6. chipgraph/adapters/format/chip_yaml.py +323 -0
  7. chipgraph/adapters/format/qsoc_contract.py +246 -0
  8. chipgraph/adapters/llm/__init__.py +58 -0
  9. chipgraph/adapters/llm/_budget.py +173 -0
  10. chipgraph/adapters/llm/_errors.py +50 -0
  11. chipgraph/adapters/llm/_nda.py +30 -0
  12. chipgraph/adapters/llm/_retry.py +85 -0
  13. chipgraph/adapters/llm/anthropic.py +247 -0
  14. chipgraph/adapters/llm/decide_backend.py +179 -0
  15. chipgraph/adapters/llm/fake.py +87 -0
  16. chipgraph/adapters/llm/guard.py +68 -0
  17. chipgraph/adapters/parser/__init__.py +22 -0
  18. chipgraph/adapters/parser/cocotb.py +304 -0
  19. chipgraph/adapters/parser/generic_regex.py +75 -0
  20. chipgraph/adapters/parser/verible.py +55 -0
  21. chipgraph/adapters/parser/verilator.py +46 -0
  22. chipgraph/adapters/review/__init__.py +7 -0
  23. chipgraph/adapters/review/file.py +123 -0
  24. chipgraph/adapters/review/github.py +886 -0
  25. chipgraph/adapters/runner/__init__.py +7 -0
  26. chipgraph/adapters/runner/local.py +122 -0
  27. chipgraph/adapters/runtime/__init__.py +5 -0
  28. chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
  29. chipgraph/adapters/runtime/claude_code/agents.py +158 -0
  30. chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
  31. chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
  32. chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
  33. chipgraph/adapters/runtime/claude_code/loop.py +342 -0
  34. chipgraph/adapters/runtime/claude_code/previous.py +419 -0
  35. chipgraph/adapters/runtime/claude_code/review.py +492 -0
  36. chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
  37. chipgraph/adapters/runtime/claude_code/service.py +2285 -0
  38. chipgraph/adapters/tool/__init__.py +7 -0
  39. chipgraph/adapters/tool/cmd.py +229 -0
  40. chipgraph/adapters/tool/edalize.py +773 -0
  41. chipgraph/adapters/tool/filelist.py +259 -0
  42. chipgraph/adapters/tool/pyslang.py +485 -0
  43. chipgraph/adapters/vcs/__init__.py +1 -0
  44. chipgraph/adapters/vcs/git.py +521 -0
  45. chipgraph/app/__init__.py +10 -0
  46. chipgraph/app/baseline.py +339 -0
  47. chipgraph/app/build.py +332 -0
  48. chipgraph/app/checks.py +202 -0
  49. chipgraph/app/context.py +191 -0
  50. chipgraph/app/errors.py +11 -0
  51. chipgraph/app/executors.py +79 -0
  52. chipgraph/app/findings.py +203 -0
  53. chipgraph/app/ingest.py +523 -0
  54. chipgraph/checks/__init__.py +52 -0
  55. chipgraph/checks/_cdc_analyze.py +272 -0
  56. chipgraph/checks/_cdc_elaborate.py +455 -0
  57. chipgraph/checks/_common.py +268 -0
  58. chipgraph/checks/_cross.py +96 -0
  59. chipgraph/checks/_model.py +64 -0
  60. chipgraph/checks/_naming_pyslang.py +235 -0
  61. chipgraph/checks/_naming_rules.py +127 -0
  62. chipgraph/checks/_xref_syntax.py +260 -0
  63. chipgraph/checks/cdc_struct.py +235 -0
  64. chipgraph/checks/connect.py +353 -0
  65. chipgraph/checks/cross_chip.py +454 -0
  66. chipgraph/checks/duplicate.py +153 -0
  67. chipgraph/checks/filelist.py +323 -0
  68. chipgraph/checks/format.py +268 -0
  69. chipgraph/checks/generated.py +151 -0
  70. chipgraph/checks/hardcode.py +233 -0
  71. chipgraph/checks/layout.py +155 -0
  72. chipgraph/checks/naming.py +311 -0
  73. chipgraph/checks/plan_check.py +901 -0
  74. chipgraph/checks/ports_diff.py +203 -0
  75. chipgraph/checks/spec_schema.py +277 -0
  76. chipgraph/checks/style.py +910 -0
  77. chipgraph/checks/tb_static.py +599 -0
  78. chipgraph/checks/trace.py +224 -0
  79. chipgraph/checks/vendor_guard.py +369 -0
  80. chipgraph/checks/wrap_check.py +486 -0
  81. chipgraph/cli/__init__.py +1688 -0
  82. chipgraph/cli/next_steps.py +137 -0
  83. chipgraph/core/__init__.py +1 -0
  84. chipgraph/core/config/__init__.py +93 -0
  85. chipgraph/core/config/defaults.py +14 -0
  86. chipgraph/core/config/errors.py +32 -0
  87. chipgraph/core/config/loader.py +766 -0
  88. chipgraph/core/config/models.py +845 -0
  89. chipgraph/core/config/templates.py +340 -0
  90. chipgraph/core/contracts/__init__.py +75 -0
  91. chipgraph/core/contracts/_paths.py +26 -0
  92. chipgraph/core/contracts/agent.py +49 -0
  93. chipgraph/core/contracts/artifact.py +68 -0
  94. chipgraph/core/contracts/check.py +76 -0
  95. chipgraph/core/contracts/event.py +70 -0
  96. chipgraph/core/contracts/export.py +128 -0
  97. chipgraph/core/contracts/finding.py +171 -0
  98. chipgraph/core/contracts/gate.py +50 -0
  99. chipgraph/core/contracts/rule.py +177 -0
  100. chipgraph/core/contracts/types.py +45 -0
  101. chipgraph/core/engine/__init__.py +70 -0
  102. chipgraph/core/engine/agent_rule.py +1362 -0
  103. chipgraph/core/engine/baseline.py +283 -0
  104. chipgraph/core/engine/decide.py +462 -0
  105. chipgraph/core/engine/fanout.py +1358 -0
  106. chipgraph/core/engine/gate.py +170 -0
  107. chipgraph/core/engine/graph.py +647 -0
  108. chipgraph/core/engine/records.py +60 -0
  109. chipgraph/core/engine/rules.py +92 -0
  110. chipgraph/core/engine/scheduler.py +637 -0
  111. chipgraph/core/engine/select.py +1239 -0
  112. chipgraph/core/engine/targets.py +342 -0
  113. chipgraph/core/model/__init__.py +98 -0
  114. chipgraph/core/model/entities.py +244 -0
  115. chipgraph/core/model/export.py +16 -0
  116. chipgraph/core/model/ingest.py +693 -0
  117. chipgraph/core/model/json_value.py +4 -0
  118. chipgraph/core/model/keys.py +95 -0
  119. chipgraph/core/model/model.py +153 -0
  120. chipgraph/core/model/provenance.py +27 -0
  121. chipgraph/core/model/query.py +583 -0
  122. chipgraph/core/model/registry.py +80 -0
  123. chipgraph/core/model/relations.py +61 -0
  124. chipgraph/core/model/store.py +279 -0
  125. chipgraph/core/plugin_api/__init__.py +81 -0
  126. chipgraph/core/plugin_api/local.py +167 -0
  127. chipgraph/core/plugin_api/pack.py +159 -0
  128. chipgraph/core/plugin_api/protocols.py +177 -0
  129. chipgraph/core/plugin_api/registry.py +111 -0
  130. chipgraph/core/plugin_api/types.py +94 -0
  131. chipgraph/core/runtime/__init__.py +41 -0
  132. chipgraph/core/runtime/executor.py +79 -0
  133. chipgraph/core/runtime/queue.py +565 -0
  134. chipgraph/core/runtime/roles/__init__.py +91 -0
  135. chipgraph/core/runtime/roles/data/author.md +41 -0
  136. chipgraph/core/runtime/roles/data/critic.md +56 -0
  137. chipgraph/core/runtime/roles/data/planner.md +38 -0
  138. chipgraph/core/runtime/roles/data/researcher.md +37 -0
  139. chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
  140. chipgraph/core/runtime/roles/data/tb-author.md +48 -0
  141. chipgraph/core/runtime/roles/data/triage.md +34 -0
  142. chipgraph/core/runtime/roles/policy.py +299 -0
  143. chipgraph/core/runtime/roles/registry.py +52 -0
  144. chipgraph/core/runtime/roles/review.py +275 -0
  145. chipgraph/core/runtime/roles/schema.py +82 -0
  146. chipgraph/core/runtime/roles/skills.py +134 -0
  147. chipgraph/core/runtime/roles/spec.py +232 -0
  148. chipgraph/core/state/__init__.py +1 -0
  149. chipgraph/core/state/artifacts.py +156 -0
  150. chipgraph/core/state/backend.py +117 -0
  151. chipgraph/core/state/findings.py +151 -0
  152. chipgraph/core/state/handoff.py +411 -0
  153. chipgraph/core/state/idempotency.py +59 -0
  154. chipgraph/core/state/journal.py +238 -0
  155. chipgraph/core/state/layout.py +82 -0
  156. chipgraph/core/state/lock.py +195 -0
  157. chipgraph/core/state/trace.py +225 -0
  158. chipgraph/learn/__init__.py +96 -0
  159. chipgraph/learn/draft.py +187 -0
  160. chipgraph/learn/infer.py +580 -0
  161. chipgraph/learn/models.py +145 -0
  162. chipgraph/learn/try_run.py +228 -0
  163. chipgraph/mcp/__init__.py +12 -0
  164. chipgraph/mcp/model_tools.py +167 -0
  165. chipgraph/mcp/server.py +787 -0
  166. chipgraph/orgs/qnsc/naming-v1.yml +113 -0
  167. chipgraph/orgs/qnsc/profile.yml +5 -0
  168. chipgraph/packs/__init__.py +8 -0
  169. chipgraph/packs/assist/__init__.py +16 -0
  170. chipgraph/packs/assist/ask/__init__.py +58 -0
  171. chipgraph/packs/assist/ask/_db.py +57 -0
  172. chipgraph/packs/assist/ask/_project.py +230 -0
  173. chipgraph/packs/assist/ask/answer.py +266 -0
  174. chipgraph/packs/assist/ask/check.py +182 -0
  175. chipgraph/packs/assist/ask/contract.py +115 -0
  176. chipgraph/packs/assist/ask/documents.py +157 -0
  177. chipgraph/packs/assist/ask/fts.py +80 -0
  178. chipgraph/packs/assist/ask/retrieve.py +331 -0
  179. chipgraph/packs/assist/audit/__init__.py +383 -0
  180. chipgraph/packs/assist/pack.yml +13 -0
  181. chipgraph/packs/assist/triage/__init__.py +64 -0
  182. chipgraph/packs/assist/triage/contract.py +94 -0
  183. chipgraph/packs/assist/triage/parse.py +371 -0
  184. chipgraph/packs/assist/triage/question.py +384 -0
  185. chipgraph/packs/assist/triage/report.py +160 -0
  186. chipgraph/packs/assist/triage/rules.py +447 -0
  187. chipgraph/packs/assist/triage/run.py +224 -0
  188. chipgraph/packs/digital_rtl/__init__.py +19 -0
  189. chipgraph/packs/digital_rtl/layout.py +144 -0
  190. chipgraph/packs/digital_rtl/pack.yml +25 -0
  191. chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
  192. chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
  193. chipgraph/packs/digital_rtl/plan/context.py +82 -0
  194. chipgraph/packs/digital_rtl/plan/expand.py +94 -0
  195. chipgraph/packs/digital_rtl/plan/model.py +150 -0
  196. chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
  197. chipgraph/packs/digital_rtl/plan/show.py +176 -0
  198. chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
  199. chipgraph/packs/digital_rtl/pr/body.py +165 -0
  200. chipgraph/packs/digital_rtl/pr/gate.py +110 -0
  201. chipgraph/packs/digital_rtl/pr/open.py +397 -0
  202. chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
  203. chipgraph/packs/digital_rtl/rtl/context.py +272 -0
  204. chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
  205. chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
  206. chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
  207. chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
  208. chipgraph/packs/digital_rtl/rules/review.yml +31 -0
  209. chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
  210. chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
  211. chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
  212. chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
  213. chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
  214. chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
  215. chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
  216. chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
  217. chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
  218. chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
  219. chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
  220. chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
  221. chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
  222. chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
  223. chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
  224. chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
  225. chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
  226. chipgraph/packs/dv/__init__.py +15 -0
  227. chipgraph/packs/dv/declaration.py +392 -0
  228. chipgraph/packs/dv/interface.py +596 -0
  229. chipgraph/packs/dv/pack.yml +14 -0
  230. chipgraph/packs/dv/rules/tb_module.yml +42 -0
  231. chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
  232. chipgraph/packs/spec_core/__init__.py +9 -0
  233. chipgraph/packs/spec_core/extract/__init__.py +12 -0
  234. chipgraph/packs/spec_core/extract/_markdown.py +371 -0
  235. chipgraph/packs/spec_core/extract/_text.py +116 -0
  236. chipgraph/packs/spec_core/extract/mas.py +975 -0
  237. chipgraph/packs/spec_core/gen/__init__.py +11 -0
  238. chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
  239. chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
  240. chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
  241. chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
  242. chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
  243. chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
  244. chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
  245. chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
  246. chipgraph/packs/spec_core/pack.yml +6 -0
  247. chipgraph/py.typed +0 -0
  248. chipgraph-0.1.0.dist-info/METADATA +53 -0
  249. chipgraph-0.1.0.dist-info/RECORD +252 -0
  250. chipgraph-0.1.0.dist-info/WHEEL +4 -0
  251. chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
  252. chipgraph-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,7 @@
1
+ """Tool adapters: wrap external EDA (or other command-line) tools behind `ToolAdapter`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from chipgraph.adapters.tool.cmd import CmdTool
6
+
7
+ __all__ = ["CmdTool"]
@@ -0,0 +1,229 @@
1
+ """`CmdTool`: a `ToolAdapter` that runs a project's own command line, e.g.
2
+
3
+ ``make lint BLOCK={block}``, and reads its log through a `LogParser`. This lets a
4
+ project with its own EDA flow plug straight into chipgraph without a dedicated
5
+ adapter (DESIGN.md 7.1).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import shlex
12
+ import time
13
+ from collections.abc import Sequence
14
+ from pathlib import Path
15
+
16
+ from chipgraph.adapters.parser import PARSERS, GenericRegexParser
17
+ from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
18
+ from chipgraph.core.contracts.types import CheckStatus
19
+ from chipgraph.core.plugin_api.protocols import LogParser
20
+ from chipgraph.core.plugin_api.types import ToolContext
21
+
22
+ _DEFAULT_OK_RETURNCODES = (0,)
23
+ _LOG_TAIL_MAX_CHARS = 4000
24
+ _MISSING_EXECUTABLE_RETURNCODE = 127
25
+
26
+
27
+ class CmdTool:
28
+ """Runs `spec.args["cmd"]` through `ctx.runner` and parses its log into `Issue`s."""
29
+
30
+ name = "cmd"
31
+ capability = "any"
32
+
33
+ async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
34
+ start = time.monotonic()
35
+
36
+ try:
37
+ argv = _build_argv(spec, ctx)
38
+ except _CmdArgError as exc:
39
+ return CheckResult(
40
+ check_id=spec.id,
41
+ status="error",
42
+ issues=(Issue(msg=str(exc), severity="error"),),
43
+ duration_s=time.monotonic() - start,
44
+ idempotency_key=_hash_key(spec, ctx, argv=None),
45
+ )
46
+
47
+ cwd = _resolve_cwd(spec, ctx)
48
+ timeout_s = spec.args.get("timeout_s")
49
+ ok_returncodes = tuple(spec.args.get("ok_returncodes", _DEFAULT_OK_RETURNCODES))
50
+
51
+ result = await ctx.runner.run(argv, cwd=cwd, env=ctx.env, timeout_s=timeout_s)
52
+
53
+ log = result.stdout + result.stderr
54
+ log_tail = log[-_LOG_TAIL_MAX_CHARS:]
55
+
56
+ try:
57
+ parser = _build_parser(spec)
58
+ except _CmdArgError as exc:
59
+ return CheckResult(
60
+ check_id=spec.id,
61
+ status="error",
62
+ issues=(Issue(msg=str(exc), severity="error"),),
63
+ log_tail=log_tail,
64
+ duration_s=time.monotonic() - start,
65
+ idempotency_key=_hash_key(spec, ctx, argv=argv),
66
+ )
67
+
68
+ issues = parser.parse(log) if parser is not None else ()
69
+
70
+ duration_s = time.monotonic() - start
71
+ idempotency_key = _hash_key(spec, ctx, argv=argv)
72
+
73
+ if result.timed_out:
74
+ timeout_issue = Issue(
75
+ msg=f"command timed out after {timeout_s}s: {' '.join(argv)}",
76
+ severity="error",
77
+ )
78
+ return CheckResult(
79
+ check_id=spec.id,
80
+ status="error",
81
+ issues=(*issues, timeout_issue),
82
+ log_tail=log_tail,
83
+ duration_s=duration_s,
84
+ idempotency_key=idempotency_key,
85
+ )
86
+
87
+ if result.returncode == _MISSING_EXECUTABLE_RETURNCODE:
88
+ missing_issue = Issue(
89
+ msg=f"command not found: {' '.join(argv)}",
90
+ severity="error",
91
+ )
92
+ return CheckResult(
93
+ check_id=spec.id,
94
+ status="error",
95
+ issues=(*issues, missing_issue),
96
+ log_tail=log_tail,
97
+ duration_s=duration_s,
98
+ idempotency_key=idempotency_key,
99
+ )
100
+
101
+ has_error_issue = any(issue.severity == "error" for issue in issues)
102
+
103
+ if result.returncode not in ok_returncodes:
104
+ if not issues:
105
+ issues = (
106
+ Issue(
107
+ msg=f"command exited with {result.returncode}",
108
+ severity="error",
109
+ ),
110
+ )
111
+ return CheckResult(
112
+ check_id=spec.id,
113
+ status="fail",
114
+ issues=issues,
115
+ log_tail=log_tail,
116
+ duration_s=duration_s,
117
+ idempotency_key=idempotency_key,
118
+ )
119
+
120
+ status: CheckStatus = "fail" if has_error_issue else "pass"
121
+ return CheckResult(
122
+ check_id=spec.id,
123
+ status=status,
124
+ issues=issues,
125
+ log_tail=log_tail,
126
+ duration_s=duration_s,
127
+ idempotency_key=idempotency_key,
128
+ )
129
+
130
+ @staticmethod
131
+ def key_for(spec: CheckSpec, ctx: ToolContext) -> str:
132
+ """A stable idempotency key for this check invocation.
133
+
134
+ Hashes the substituted argv, the (repo-relative) cwd, the sorted `ctx.env`
135
+ items and `ctx.params`. Never raises: an invalid or unsubstitutable `cmd`
136
+ (the same case that makes `run` return an `error` result) still yields a key,
137
+ it is just keyed on the raw, unsubstituted `args["cmd"]` instead.
138
+ """
139
+ try:
140
+ argv: tuple[str, ...] | None = tuple(_build_argv(spec, ctx))
141
+ except _CmdArgError:
142
+ argv = None
143
+ return _hash_key(spec, ctx, argv=argv)
144
+
145
+
146
+ class _CmdArgError(Exception):
147
+ """Raised for a malformed `CheckSpec.args`, turned into an `error` `CheckResult`."""
148
+
149
+
150
+ def _hash_key(spec: CheckSpec, ctx: ToolContext, *, argv: Sequence[str] | None) -> str:
151
+ cwd = spec.args.get("cwd", "")
152
+ digest_input = repr(
153
+ (
154
+ spec.id,
155
+ tuple(argv) if argv is not None else repr(spec.args.get("cmd")),
156
+ str(cwd),
157
+ tuple(sorted(ctx.env.items())),
158
+ tuple(sorted(ctx.params.items())),
159
+ )
160
+ )
161
+ return hashlib.sha256(digest_input.encode("utf-8")).hexdigest()
162
+
163
+
164
+ def _build_argv(spec: CheckSpec, ctx: ToolContext) -> list[str]:
165
+ cmd = spec.args.get("cmd")
166
+ if cmd is None:
167
+ raise _CmdArgError(f"check {spec.id!r}: args['cmd'] is required")
168
+
169
+ if isinstance(cmd, str):
170
+ try:
171
+ template = cmd.format(**ctx.params)
172
+ except KeyError as exc:
173
+ raise _CmdArgError(
174
+ f"check {spec.id!r}: missing param {exc.args[0]!r} for cmd template {cmd!r}"
175
+ ) from exc
176
+ return shlex.split(template)
177
+
178
+ if isinstance(cmd, list):
179
+ argv: list[str] = []
180
+ for token in cmd:
181
+ try:
182
+ argv.append(str(token).format(**ctx.params))
183
+ except KeyError as exc:
184
+ raise _CmdArgError(
185
+ f"check {spec.id!r}: missing param {exc.args[0]!r} for cmd token {token!r}"
186
+ ) from exc
187
+ return argv
188
+
189
+ raise _CmdArgError(f"check {spec.id!r}: args['cmd'] must be a string or a list of strings")
190
+
191
+
192
+ def _resolve_cwd(spec: CheckSpec, ctx: ToolContext) -> Path:
193
+ cwd_arg = spec.args.get("cwd")
194
+ if cwd_arg is None:
195
+ return ctx.repo_root
196
+ return ctx.repo_root / str(cwd_arg)
197
+
198
+
199
+ def _build_parser(spec: CheckSpec) -> LogParser | None:
200
+ parser_name = spec.args.get("parser")
201
+ regex = spec.args.get("regex")
202
+
203
+ if parser_name is not None and regex is not None:
204
+ raise _CmdArgError(
205
+ f"check {spec.id!r}: args['parser'] and args['regex'] are mutually exclusive"
206
+ )
207
+
208
+ if regex is not None:
209
+ severity_group = spec.args.get("severity_group")
210
+ default_severity = spec.args.get("default_severity", "error")
211
+ try:
212
+ return GenericRegexParser(
213
+ pattern=regex,
214
+ severity_group=severity_group,
215
+ default_severity=default_severity,
216
+ )
217
+ except ValueError as exc:
218
+ raise _CmdArgError(f"check {spec.id!r}: {exc}") from exc
219
+
220
+ if parser_name is not None:
221
+ factory = PARSERS.get(parser_name)
222
+ if factory is None:
223
+ raise _CmdArgError(
224
+ f"check {spec.id!r}: unknown parser {parser_name!r}; "
225
+ f"known parsers: {sorted(PARSERS)}"
226
+ )
227
+ return factory()
228
+
229
+ return None