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,144 @@
1
+ """Where a block's RTL and testbench files go: from the profile's `layout` (M2-11a).
2
+
3
+ A project says where each kind of file lives with `layout:` templates (and per block,
4
+ `blocks.<b>.layout`). The built-in rules write new files there:
5
+
6
+ - `digital-rtl/rtl_module` writes `{layout.rtl}/{module}.sv`;
7
+ - `dv/tb_module` writes `{layout.tb}/test_{block}.py`;
8
+ - a plan's write sets must stay inside the block's `rtl` and `tb` layout paths.
9
+
10
+ `layout_dir(resolved, block, kind)` is the directory part of the block's first `kind`
11
+ template: `{block}`/`{BLOCK}` filled in, cut before the file name and before the first
12
+ segment with a glob or another placeholder. QSoC's `design/{block}/rtl/*.sv` gives
13
+ `design/pwm/rtl` for `pwm`; tinysoc's `rtl/*.sv` gives `rtl`; `dv/{block}/**` gives
14
+ `dv/timer`. With a list of templates the first one is where new files go.
15
+
16
+ `layout_vars(resolved, block)` is what the graph fills `{layout.rtl}` and `{layout.tb}`
17
+ with (template values, not params: they never change an instance id). A project with no
18
+ `rtl` or `tb` layout key keeps the built-in defaults, `rtl` and `dv/{block}`.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ from collections.abc import Mapping
25
+
26
+ from chipgraph.core.config.loader import ResolvedProfile
27
+ from chipgraph.core.config.models import Profile
28
+
29
+ LayoutValue = str | tuple[str, ...]
30
+
31
+ RTL_VAR = "layout.rtl"
32
+ """The template value of the block's RTL directory."""
33
+ TB_VAR = "layout.tb"
34
+ """The template value of the block's testbench directory."""
35
+
36
+ DEFAULT_DIRS: Mapping[str, str] = {"rtl": "rtl", "tb": "dv/{block}"}
37
+ """The directories the built-in rules use when the profile has no layout key for a kind."""
38
+
39
+ _KINDS: Mapping[str, str] = {RTL_VAR: "rtl", TB_VAR: "tb"}
40
+ _DYNAMIC = re.compile(r"[*?\[\]{}]")
41
+
42
+
43
+ class LayoutError(ValueError):
44
+ """The profile's layout does not say where a kind of file goes."""
45
+
46
+
47
+ def _profile(resolved: ResolvedProfile | Profile, block: str) -> Profile:
48
+ if isinstance(resolved, ResolvedProfile):
49
+ return resolved.for_block(block)
50
+ override = resolved.blocks.get(block)
51
+ if override is not None and override.layout:
52
+ return resolved.model_copy(update={"layout": {**resolved.layout, **override.layout}})
53
+ return resolved
54
+
55
+
56
+ def _fill(template: str, block: str) -> str:
57
+ return template.replace("{block}", block).replace("{BLOCK}", block.upper())
58
+
59
+
60
+ def templates(resolved: ResolvedProfile | Profile, block: str, kind: str) -> tuple[str, ...]:
61
+ """The block's `kind` layout templates, `{block}`/`{BLOCK}` filled in (maybe none)."""
62
+ value: LayoutValue | None = _profile(resolved, block).layout.get(kind)
63
+ if value is None:
64
+ return ()
65
+ raw = (value,) if isinstance(value, str) else tuple(value)
66
+ return tuple(_fill(t, block) for t in raw)
67
+
68
+
69
+ def template_dir(template: str) -> str:
70
+ """The directory part of a filled layout template ('' when it has none)."""
71
+ parts = template.split("/")
72
+ out: list[str] = []
73
+ for part in parts[:-1]: # the last segment is the file (or `**`)
74
+ if not part or part == "." or _DYNAMIC.search(part):
75
+ break
76
+ out.append(part)
77
+ return "/".join(out)
78
+
79
+
80
+ def layout_dir(resolved: ResolvedProfile | Profile, block: str, kind: str) -> str:
81
+ """The directory new `kind` files of `block` go to (see the module docstring).
82
+
83
+ Raises `LayoutError` when the block's layout has no `kind` key, or its first
84
+ template has no directory part.
85
+ """
86
+ found = templates(resolved, block, kind)
87
+ if not found:
88
+ raise LayoutError(
89
+ f"block {block!r} has no layout.{kind}: say where its {kind} files go in "
90
+ f'.chipgraph.yml, e.g. `layout: {{ {kind}: "<dir>/*" }}`'
91
+ )
92
+ directory = template_dir(found[0])
93
+ if not directory or directory == ".." or directory.startswith("../"):
94
+ raise LayoutError(
95
+ f"block {block!r}: layout.{kind} {found[0]!r} has no directory part before its "
96
+ "first glob or placeholder; start it with the directory, e.g. 'rtl/*.sv'"
97
+ )
98
+ return directory
99
+
100
+
101
+ def dir_or_default(resolved: ResolvedProfile | Profile, block: str, kind: str) -> str:
102
+ """`layout_dir`, or the built-in default (`DEFAULT_DIRS`) when the key is missing."""
103
+ if not templates(resolved, block, kind):
104
+ return _fill(DEFAULT_DIRS[kind], block)
105
+ return layout_dir(resolved, block, kind)
106
+
107
+
108
+ def layout_vars(resolved: ResolvedProfile | Profile, block: str) -> dict[str, str]:
109
+ """`{layout.rtl}` and `{layout.tb}` for `block` (raises `LayoutError` on a bad template)."""
110
+ return {var: dir_or_default(resolved, block, kind) for var, kind in _KINDS.items()}
111
+
112
+
113
+ def write_globs(resolved: ResolvedProfile | Profile, block: str) -> tuple[str, ...]:
114
+ """The paths a plan of `block` may write: its `rtl` and `tb` layout templates.
115
+
116
+ A kind with no layout key allows everything under its default directory.
117
+ """
118
+ out: list[str] = []
119
+ for kind in ("rtl", "tb"):
120
+ found = templates(resolved, block, kind)
121
+ out.extend(found or (f"{_fill(DEFAULT_DIRS[kind], block)}/**",))
122
+ return tuple(dict.fromkeys(out))
123
+
124
+
125
+ def fill_vars(template: str, values: Mapping[str, str]) -> str:
126
+ """`template` with each `{name}` of `values` replaced; other placeholders stay."""
127
+ for name, value in values.items():
128
+ template = template.replace("{" + name + "}", value)
129
+ return template
130
+
131
+
132
+ __all__ = [
133
+ "DEFAULT_DIRS",
134
+ "RTL_VAR",
135
+ "TB_VAR",
136
+ "LayoutError",
137
+ "dir_or_default",
138
+ "fill_vars",
139
+ "layout_dir",
140
+ "layout_vars",
141
+ "template_dir",
142
+ "templates",
143
+ "write_globs",
144
+ ]
@@ -0,0 +1,25 @@
1
+ schema_version: 1
2
+ name: digital-rtl
3
+ version: 0.1.0
4
+ description: >-
5
+ Rules and skills for digital RTL (DESIGN.md 8.2). Ships the Critic review of a block's
6
+ change (the rule `digital-rtl/review`, the skill `review/diff`) and the Planner: the
7
+ rule `digital-rtl/plan` (skill `plan/modules`, check `plan_check`) and the gate
8
+ `plan:{block}` on `digital-rtl/plan_expand`, after which rules may say
9
+ `foreach: plan.modules`; and the RTL of each planned module: the rule
10
+ `digital-rtl/rtl_module` (role `rtl-author`, which never sees a testbench; skills
11
+ `lang-sv/rtl` and `org/naming`; checks format, lint, naming, style, hardcode). The
12
+ target `rtl:<block>` (`targets/`) ties them to the testbench of the `dv` pack, the review and
13
+ a pull request: `open_pr` pushes the block's branch and opens the PR, `pr_gate` waits on
14
+ the gate `pr:{block}`; chipgraph never merges. Where a module goes comes from the profile's
15
+ `layout.rtl`, the testbench's from `layout.tb`. Also the wrappers around vendor IP in the
16
+ emacs verilog-mode AUTO style: the rule `digital-rtl/wrapper` (role `rtl-author`, skill
17
+ `rtl/wrapper-auto`) writes a wrapper's source, `digital-rtl/wrapper_gen` (tool `emacs_auto`)
18
+ expands it (needs Emacs 31 or newer), check `wrap_check`.
19
+ provides:
20
+ rules:
21
+ - rules
22
+ - wrapper/rules
23
+ skills:
24
+ - skills
25
+ - wrapper/skills
@@ -0,0 +1,34 @@
1
+ """A block's plan: the file a Planner writes, and what the engine does with it (M2-03).
2
+
3
+ - `model`: the plan file (`Plan`, `schemas/formats/plan.schema.json`);
4
+ - `chipgraph.checks.plan_check`: `plan_check`, the validation before a person approves;
5
+ - `resolver`: the `foreach: plan.modules` expansion, only from an approved plan;
6
+ - `expand`: the `plan_expand` tool behind the gate `plan:<block>`;
7
+ - `show`: the summary `chipgraph plan show <block>` prints for the approver;
8
+ - `context`: what a `planner` task gets in its context.
9
+
10
+ Only the model is imported here: the other modules import the check, which imports the
11
+ model, so importing them from this package's `__init__` would make a cycle.
12
+ """
13
+
14
+ from chipgraph.packs.digital_rtl.plan.model import (
15
+ ModuleBudget,
16
+ Plan,
17
+ PlanInterface,
18
+ PlanModule,
19
+ PlanPort,
20
+ PlanWrite,
21
+ UnassignedReq,
22
+ schema_json,
23
+ )
24
+
25
+ __all__ = [
26
+ "ModuleBudget",
27
+ "Plan",
28
+ "PlanInterface",
29
+ "PlanModule",
30
+ "PlanPort",
31
+ "PlanWrite",
32
+ "UnassignedReq",
33
+ "schema_json",
34
+ ]
@@ -0,0 +1,16 @@
1
+ """`python -m chipgraph.packs.digital_rtl.plan [PATH]`: write the plan file JSON Schema."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from chipgraph.packs.digital_rtl.plan.model import schema_json
7
+
8
+
9
+ def main() -> None:
10
+ out = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("schemas/formats/plan.schema.json")
11
+ out.write_text(schema_json(), encoding="utf-8")
12
+ print(f"wrote {out}")
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
@@ -0,0 +1,82 @@
1
+ """What a `planner` task gets in its context, on top of its inputs (DESIGN.md 5.1, 5.3).
2
+
3
+ So the reply is a valid plan the first time: the block's slice of the Design Model
4
+ (requirements, interface ports, existing modules and their ports), the layout paths a
5
+ module may write, the files that already exist there and those other rules produce,
6
+ the files each module's writes must include, the limits, the naming rule for module
7
+ names, and the plan file's JSON Schema.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Sequence
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from chipgraph.checks._common import compile_template, iter_repo_files
17
+ from chipgraph.checks.plan_check import PlanEnv, PlanEnvError, load_env
18
+ from chipgraph.core.config.loader import ResolvedProfile
19
+ from chipgraph.core.contracts import RuleSpec
20
+ from chipgraph.packs.digital_rtl.plan.model import Plan
21
+
22
+
23
+ def _ports(ports: Sequence[Any]) -> list[dict[str, Any]]:
24
+ return [{"name": p.name, "direction": p.direction, "width": p.width} for p in ports]
25
+
26
+
27
+ def _naming(env: PlanEnv) -> dict[str, Any] | None:
28
+ if env.naming is None:
29
+ return None
30
+ module = env.naming.kind_patterns.get("module")
31
+ return {
32
+ "rules": env.naming_ref,
33
+ "module": (
34
+ {"rule": module[0], "pattern": module[1].pattern, "message": module[2]}
35
+ if module is not None
36
+ else None
37
+ ),
38
+ "vocabulary": dict(env.naming.vocabulary.replace) if env.naming.vocabulary else {},
39
+ }
40
+
41
+
42
+ def planner_context(
43
+ root: Path,
44
+ resolved: ResolvedProfile,
45
+ rules: Sequence[RuleSpec],
46
+ block: str,
47
+ *,
48
+ plan_path: str,
49
+ ) -> dict[str, Any]:
50
+ """The extra context of block `block`'s planner task (`get_context` key `plan`)."""
51
+ try:
52
+ env = load_env(root, block, resolved=resolved, rules=rules)
53
+ except PlanEnvError as exc:
54
+ return {"block": block, "plan_path": plan_path, "note": str(exc)}
55
+ layout = [compile_template(t) for t in env.layout]
56
+ in_layout = [f for f in iter_repo_files(root) if any(t.match(f) is not None for t in layout)]
57
+ taken = {**env.agent_outputs, **env.engine_outputs}
58
+ return {
59
+ "block": block,
60
+ "plan_path": plan_path,
61
+ "requirements": [
62
+ {"id": r.ref, "declared": r.declared, "text": r.text} for r in env.requirements
63
+ ],
64
+ "interface": {"source": env.interface_source, "ports": _ports(env.interface)},
65
+ "existing_modules": [
66
+ {"name": m.name, "file": m.file, "ports": _ports(m.ports)} for m in env.existing_modules
67
+ ],
68
+ "layout": list(env.layout),
69
+ "existing_files": [f for f in in_layout if f not in taken],
70
+ "other_rules_write": sorted(
71
+ p for p in taken if any(t.match(p) is not None for t in layout)
72
+ ),
73
+ "module_outputs": [
74
+ {"rule": rule_id, "template": template} for rule_id, template in env.module_outputs
75
+ ],
76
+ "limits": env.limits.model_dump(mode="json"),
77
+ "naming": _naming(env),
78
+ "schema": Plan.model_json_schema(),
79
+ }
80
+
81
+
82
+ __all__ = ["planner_context"]
@@ -0,0 +1,94 @@
1
+ """`PlanExpandTool`: the `plan_expand` tool of rule `digital-rtl/plan_expand`.
2
+
3
+ The rule is gated by `plan:{block}`, so the scheduler runs this only once a person has
4
+ approved the plan at its current hash. It checks the plan once more (all of
5
+ `plan_check`, existing files included) and writes the approved plan artifact
6
+ (`plan/<block>.approved.json` by default): the block, the plan's path and hash, the
7
+ modules in build order with their dependencies and write sets. Every `plan.modules`
8
+ instance takes that artifact as an input, so it runs after this step.
9
+
10
+ Rule args (both `{block}` templates): `plan` (default `plan/{block}.plan.yml`) and
11
+ `out` (default `plan/{block}.approved.json`).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import time
18
+
19
+ from chipgraph.checks._common import compute_idempotency_key
20
+ from chipgraph.checks.plan_check import (
21
+ DEFAULT_PLAN_PATH,
22
+ PlanEnvError,
23
+ check_plan_file,
24
+ topo_order,
25
+ )
26
+ from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
27
+ from chipgraph.core.plugin_api.types import ToolContext
28
+
29
+ DEFAULT_APPROVED_PATH = "plan/{block}.approved.json"
30
+
31
+
32
+ def _result(spec: CheckSpec, status: str, issues: tuple[Issue, ...], start: float) -> CheckResult:
33
+ return CheckResult(
34
+ check_id=spec.id,
35
+ status=status, # type: ignore[arg-type]
36
+ issues=issues,
37
+ duration_s=time.monotonic() - start,
38
+ idempotency_key=compute_idempotency_key(spec.id, spec.args, ()),
39
+ )
40
+
41
+
42
+ class PlanExpandTool:
43
+ """Writes a block's approved plan artifact once its gate is approved."""
44
+
45
+ name = "plan_expand"
46
+ capability = "gen"
47
+
48
+ async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
49
+ start = time.monotonic()
50
+ block = ctx.params.get("block")
51
+ plan_t = spec.args.get("plan", DEFAULT_PLAN_PATH)
52
+ out_t = spec.args.get("out", DEFAULT_APPROVED_PATH)
53
+ if not block or not isinstance(plan_t, str) or not isinstance(out_t, str):
54
+ msg = "plan_expand needs a block param and string 'plan'/'out' templates"
55
+ return _result(spec, "error", (Issue(msg=msg),), start)
56
+ try:
57
+ report = check_plan_file(ctx.repo_root, block, plan_path=plan_t)
58
+ except PlanEnvError as exc:
59
+ return _result(spec, "error", (Issue(rule="plan.env", msg=str(exc)),), start)
60
+ if not report.exists:
61
+ issue = Issue(file=report.path, rule="plan.missing", msg=f"no plan at {report.path}")
62
+ return _result(spec, "fail", (issue,), start)
63
+ if not report.ok or report.plan is None:
64
+ errors = tuple(i for i in report.issues if i.severity == "error")
65
+ return _result(spec, "fail", errors, start)
66
+
67
+ plan = report.plan
68
+ order = topo_order(plan)
69
+ artifact = {
70
+ "schema_version": 1,
71
+ "block": block,
72
+ "plan": report.path,
73
+ "plan_sha256": report.sha256,
74
+ "top": plan.top,
75
+ "order": order,
76
+ "modules": [
77
+ {
78
+ "name": name,
79
+ "depends_on": list(module.depends_on),
80
+ "writes": list(module.write_paths),
81
+ "budget": module.budget.model_dump(mode="json"),
82
+ }
83
+ for name in order
84
+ if (module := plan.module(name)) is not None
85
+ ],
86
+ }
87
+ out = ctx.repo_root / out_t.replace("{block}", block)
88
+ out.parent.mkdir(parents=True, exist_ok=True)
89
+ out.write_text(json.dumps(artifact, indent=2, sort_keys=True) + "\n", encoding="utf-8")
90
+ warnings = tuple(i for i in report.issues if i.severity != "error")
91
+ return _result(spec, "pass", warnings, start)
92
+
93
+
94
+ __all__ = ["DEFAULT_APPROVED_PATH", "PlanExpandTool"]
@@ -0,0 +1,150 @@
1
+ """The plan file a Planner writes for one block: `plan/<block>.plan.yml` (DESIGN.md 5.1, 5.3).
2
+
3
+ A plan splits a block into modules. Each module says what it does, which requirements
4
+ it covers, its interface (ports taken from the Design Model, F1), which other modules
5
+ it depends on, the files it writes (its write set, F2) and its budget. The plan also
6
+ lists the assumptions it rests on (F4), the questions a person must answer, and every
7
+ requirement of the block it does not assign, with the reason.
8
+
9
+ The plan is data a person approves (gate `plan:<block>`) before any node it describes
10
+ can run; `plan_check` (`chipgraph.checks.plan_check`) validates it. The JSON Schema is
11
+ committed at `schemas/formats/plan.schema.json` (regenerate with
12
+ `python -m chipgraph.packs.digital_rtl.plan`).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import re
19
+ from typing import Literal
20
+
21
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
22
+
23
+ from chipgraph.core.contracts._paths import validate_relative_path
24
+ from chipgraph.core.contracts.types import ModelTier
25
+
26
+ _NAME_RE = r"^[A-Za-z_][A-Za-z0-9_]*$"
27
+ _GLOB_OR_PLACEHOLDER = re.compile(r"[*?\[\]{}]")
28
+
29
+
30
+ class _Model(BaseModel):
31
+ model_config = ConfigDict(extra="forbid", frozen=True)
32
+
33
+
34
+ class PlanPort(_Model):
35
+ """One port of a module: from the block's interface in the Design Model, or internal."""
36
+
37
+ name: str = Field(pattern=_NAME_RE, description="The port name, as in the Design Model.")
38
+ direction: Literal["input", "output", "inout"] = Field(description="The port direction.")
39
+ width: int | str = Field(description="The port width: bits, or the model's expression.")
40
+ internal: bool = Field(
41
+ default=False,
42
+ description=(
43
+ "A helper port between modules of the block, not on the block's interface. "
44
+ "Not allowed on the block's top module."
45
+ ),
46
+ )
47
+
48
+
49
+ class PlanInterface(_Model):
50
+ """A module's interface."""
51
+
52
+ ports: tuple[PlanPort, ...] = Field(default=(), description="The module's ports.")
53
+
54
+
55
+ class PlanWrite(_Model):
56
+ """One file a module writes."""
57
+
58
+ path: str = Field(description="Repo-relative POSIX path of the file.")
59
+ replaces: bool = Field(
60
+ default=False,
61
+ description="The file exists and this module replaces it (said explicitly).",
62
+ )
63
+
64
+ @field_validator("path")
65
+ @classmethod
66
+ def _check_path(cls, value: str) -> str:
67
+ validate_relative_path(value, field_name="writes.path")
68
+ if _GLOB_OR_PLACEHOLDER.search(value):
69
+ raise ValueError(f"writes.path must be a plain file path, got {value!r}")
70
+ return value
71
+
72
+
73
+ class ModuleBudget(_Model):
74
+ """How much a module's nodes may spend: tries and the model tier."""
75
+
76
+ tries: int = Field(default=3, ge=1, description="Tries the module's agent task may use.")
77
+ tier: ModelTier = Field(default="medium", description="The model tier to start with.")
78
+
79
+
80
+ class PlanModule(_Model):
81
+ """One module of the plan: one dynamic node per downstream rule."""
82
+
83
+ name: str = Field(pattern=_NAME_RE, description="The module name (an RTL identifier).")
84
+ summary: str = Field(min_length=1, description="What the module does, in a sentence or two.")
85
+ reqs: tuple[str, ...] = Field(
86
+ default=(), description="Requirements it covers: REQ ids or inferred requirement keys."
87
+ )
88
+ interface: PlanInterface = Field(
89
+ default_factory=PlanInterface, description="The module's ports."
90
+ )
91
+ depends_on: tuple[str, ...] = Field(
92
+ default=(), description="Modules of this plan that must be built first."
93
+ )
94
+ writes: tuple[PlanWrite, ...] = Field(
95
+ min_length=1, description="Every file the module's nodes write (its write set)."
96
+ )
97
+ budget: ModuleBudget = Field(
98
+ default_factory=ModuleBudget, description="The module's tries and model tier."
99
+ )
100
+
101
+ @property
102
+ def write_paths(self) -> tuple[str, ...]:
103
+ """The paths of `writes`, in order."""
104
+ return tuple(w.path for w in self.writes)
105
+
106
+
107
+ class UnassignedReq(_Model):
108
+ """A requirement of the block that no module covers, and why."""
109
+
110
+ req: str = Field(min_length=1, description="The REQ id or inferred requirement key.")
111
+ reason: str = Field(min_length=1, description="Why no module of this plan covers it.")
112
+
113
+
114
+ class Plan(_Model):
115
+ """A block's plan: its modules, their write sets and order, and what a person must read."""
116
+
117
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this file.")
118
+ block: str = Field(min_length=1, description="The block this plan is for.")
119
+ top: str = Field(description="The module that is the block's top (its interface).")
120
+ modules: tuple[PlanModule, ...] = Field(min_length=1, description="The modules.")
121
+ assumptions: tuple[str, ...] = Field(
122
+ default=(), description="Assumptions the plan rests on (F4)."
123
+ )
124
+ open_questions: tuple[str, ...] = Field(
125
+ default=(), description="Questions the approver must read and answer."
126
+ )
127
+ unassigned_reqs: tuple[UnassignedReq, ...] = Field(
128
+ default=(), description="Requirements of the block no module covers, with the reason."
129
+ )
130
+
131
+ def module(self, name: str) -> PlanModule | None:
132
+ """The module called `name`, if the plan has one."""
133
+ return next((m for m in self.modules if m.name == name), None)
134
+
135
+
136
+ def schema_json() -> str:
137
+ """The JSON Schema of a plan file, as committed under `schemas/formats/`."""
138
+ return json.dumps(Plan.model_json_schema(), indent=2, sort_keys=True) + "\n"
139
+
140
+
141
+ __all__ = [
142
+ "ModuleBudget",
143
+ "Plan",
144
+ "PlanInterface",
145
+ "PlanModule",
146
+ "PlanPort",
147
+ "PlanWrite",
148
+ "UnassignedReq",
149
+ "schema_json",
150
+ ]