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,225 @@
1
+ """Tracing: OpenTelemetry spans for runs, rule instances, checks and executor calls
2
+ (DESIGN.md 9, "Quan sát"). Attribute names follow the OpenTelemetry GenAI semantic
3
+ conventions where they apply, and `chipgraph.*` otherwise.
4
+
5
+ Off by default: every caller gets a `NoopTracer` unless it explicitly asks for one, and
6
+ `configure(..., offline=True)` (or `exporter="none"`) always turns tracing off regardless
7
+ of what else is configured (DESIGN.md 9: "offline` turns every export off").
8
+
9
+ Only `opentelemetry-api` is imported at module level, so importing this module (and
10
+ therefore `chipgraph.core`) never requires the OpenTelemetry SDK or an exporter to be
11
+ installed. `configure()` imports those lazily, only when they are actually needed, and
12
+ raises a clear error if they are missing.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ from collections.abc import Iterator
19
+ from contextlib import AbstractContextManager, contextmanager, suppress
20
+ from typing import Literal, Protocol, runtime_checkable
21
+
22
+ from opentelemetry import trace as otel_trace
23
+ from opentelemetry.trace import Span as OtelSpan
24
+ from opentelemetry.trace import TracerProvider as OtelTracerProvider
25
+
26
+ # --- attribute names ----------------------------------------------------------------
27
+
28
+ RUN_ID = "chipgraph.run.id"
29
+ TARGET = "chipgraph.target"
30
+ RULE_ID = "chipgraph.rule.id"
31
+ RULE_INSTANCE = "chipgraph.rule.instance"
32
+ RULE_KIND = "chipgraph.rule.kind"
33
+ CHECK_ID = "chipgraph.check.id"
34
+ CHECK_STATUS = "chipgraph.check.status"
35
+ FAILURE_LABEL = "chipgraph.failure.label"
36
+
37
+ # Agent/LLM calls (used from M2 onward), per the GenAI semantic conventions.
38
+ GEN_AI_OPERATION_NAME = "gen_ai.operation.name"
39
+ GEN_AI_REQUEST_MODEL = "gen_ai.request.model"
40
+ GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens"
41
+ GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
42
+
43
+ # --- span names ------------------------------------------------------------------------
44
+
45
+ SPAN_RUN = "chipgraph.run"
46
+ SPAN_RULE = "chipgraph.rule"
47
+ SPAN_CHECK = "chipgraph.check"
48
+ SPAN_EXECUTE = "chipgraph.execute"
49
+ SPAN_INVOKE_AGENT = "invoke_agent"
50
+
51
+ AttrValue = str | int | float | bool
52
+
53
+
54
+ @runtime_checkable
55
+ class SpanHandle(Protocol):
56
+ """A handle to the currently open span."""
57
+
58
+ def set(self, key: str, value: AttrValue) -> None:
59
+ """Set (or overwrite) one attribute on the span."""
60
+ ...
61
+
62
+ def error(self, message: str) -> None:
63
+ """Mark the span as failed, with `message` as the error description."""
64
+ ...
65
+
66
+
67
+ @runtime_checkable
68
+ class Tracer(Protocol):
69
+ """Opens spans. `NoopTracer` is the default everywhere; `OtelTracer` is real."""
70
+
71
+ def span(self, name: str, **attributes: AttrValue) -> AbstractContextManager[SpanHandle]:
72
+ """Open a span named `name`, with `attributes` set on it up front."""
73
+ ...
74
+
75
+ def flush(self) -> None:
76
+ """Export every finished span now. Never raises."""
77
+ ...
78
+
79
+
80
+ class _NoopSpanHandle:
81
+ """The `SpanHandle` `NoopTracer` hands out: every call is a no-op."""
82
+
83
+ __slots__ = ()
84
+
85
+ def set(self, key: str, value: AttrValue) -> None:
86
+ return None
87
+
88
+ def error(self, message: str) -> None:
89
+ return None
90
+
91
+
92
+ _NOOP_SPAN_HANDLE = _NoopSpanHandle()
93
+
94
+
95
+ class NoopTracer:
96
+ """A `Tracer` that does nothing, at near-zero cost. The default everywhere."""
97
+
98
+ @contextmanager
99
+ def span(self, name: str, **attributes: AttrValue) -> Iterator[SpanHandle]:
100
+ yield _NOOP_SPAN_HANDLE
101
+
102
+ def flush(self) -> None:
103
+ return None
104
+
105
+
106
+ class _OtelSpanHandle:
107
+ """The `SpanHandle` `OtelTracer` hands out: wraps a live OpenTelemetry `Span`."""
108
+
109
+ __slots__ = ("_span",)
110
+
111
+ def __init__(self, span: OtelSpan) -> None:
112
+ self._span = span
113
+
114
+ def set(self, key: str, value: AttrValue) -> None:
115
+ self._span.set_attribute(key, value)
116
+
117
+ def error(self, message: str) -> None:
118
+ self._span.set_status(otel_trace.Status(otel_trace.StatusCode.ERROR, message))
119
+
120
+
121
+ class OtelTracer:
122
+ """Wraps `opentelemetry.trace.get_tracer`. Real spans, real cost.
123
+
124
+ `provider` lets tests inject a `TracerProvider` wired to an in-memory exporter
125
+ instead of the process-global one.
126
+ """
127
+
128
+ def __init__(
129
+ self, service_name: str = "chipgraph", *, provider: OtelTracerProvider | None = None
130
+ ) -> None:
131
+ self._provider = provider
132
+ self._tracer = (
133
+ provider.get_tracer(service_name)
134
+ if provider is not None
135
+ else otel_trace.get_tracer(service_name)
136
+ )
137
+
138
+ @contextmanager
139
+ def span(self, name: str, **attributes: AttrValue) -> Iterator[SpanHandle]:
140
+ with self._tracer.start_as_current_span(name) as span:
141
+ for key, value in attributes.items():
142
+ span.set_attribute(key, value)
143
+ yield _OtelSpanHandle(span)
144
+
145
+ def flush(self) -> None:
146
+ """Flush the provider's span processors, so `console`/`otlp` output is not
147
+ left to the interpreter's exit. Tracing must never break a build."""
148
+ force_flush = getattr(self._provider, "force_flush", None)
149
+ if force_flush is None:
150
+ return
151
+ with suppress(Exception):
152
+ force_flush()
153
+
154
+
155
+ _SDK_HINT = "the OpenTelemetry SDK is not installed; run `pip install chipgraph[otel]`"
156
+
157
+
158
+ def configure(
159
+ exporter: Literal["none", "console", "otlp"],
160
+ *,
161
+ offline: bool = False,
162
+ endpoint: str | None = None,
163
+ ) -> Tracer:
164
+ """Build the `Tracer` a run should use.
165
+
166
+ `exporter="none"` or `offline=True` always returns `NoopTracer`, regardless of the
167
+ other arguments (DESIGN.md 9: `offline` turns every export off). `"console"` prints
168
+ spans to stdout via the SDK's `ConsoleSpanExporter`. `"otlp"` exports over OTLP/HTTP
169
+ to `endpoint` (default: the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable).
170
+ """
171
+ if offline or exporter == "none":
172
+ return NoopTracer()
173
+
174
+ try:
175
+ from opentelemetry.sdk.resources import Resource
176
+ from opentelemetry.sdk.trace import TracerProvider as SdkTracerProvider
177
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
178
+ except ImportError as exc:
179
+ raise RuntimeError(f"tracing exporter {exporter!r} requires {_SDK_HINT}") from exc
180
+
181
+ provider = SdkTracerProvider(resource=Resource.create({"service.name": "chipgraph"}))
182
+
183
+ if exporter == "console":
184
+ provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
185
+ elif exporter == "otlp":
186
+ try:
187
+ # Only in the `otel` extra, not a hard or dev dependency: not always installed.
188
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( # type: ignore[import-not-found]
189
+ OTLPSpanExporter,
190
+ )
191
+ except ImportError as exc:
192
+ raise RuntimeError(f"tracing exporter 'otlp' requires {_SDK_HINT}") from exc
193
+ target = endpoint or os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT")
194
+ provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=target)))
195
+ else:
196
+ raise ValueError(f"unknown exporter {exporter!r}")
197
+
198
+ return OtelTracer(provider=provider)
199
+
200
+
201
+ __all__ = [
202
+ "CHECK_ID",
203
+ "CHECK_STATUS",
204
+ "FAILURE_LABEL",
205
+ "GEN_AI_OPERATION_NAME",
206
+ "GEN_AI_REQUEST_MODEL",
207
+ "GEN_AI_USAGE_INPUT_TOKENS",
208
+ "GEN_AI_USAGE_OUTPUT_TOKENS",
209
+ "RULE_ID",
210
+ "RULE_INSTANCE",
211
+ "RULE_KIND",
212
+ "RUN_ID",
213
+ "SPAN_CHECK",
214
+ "SPAN_EXECUTE",
215
+ "SPAN_INVOKE_AGENT",
216
+ "SPAN_RULE",
217
+ "SPAN_RUN",
218
+ "TARGET",
219
+ "AttrValue",
220
+ "NoopTracer",
221
+ "OtelTracer",
222
+ "SpanHandle",
223
+ "Tracer",
224
+ "configure",
225
+ ]
@@ -0,0 +1,96 @@
1
+ """`chipgraph learn` / `chipgraph try`: infer a project's conventions (DESIGN.md 8.6 V1).
2
+
3
+ `learn(root)` reads an existing repository and returns a `LearnResult`: a draft profile
4
+ plus the evidence and coverage behind each inferred rule. `draft` turns that into a
5
+ `.chipgraph.yml` and a naming-rules file; `try_run` runs chipgraph read-only against a
6
+ repo; `init_from_learn` writes the reviewed draft into a project as its `.chipgraph.yml`.
7
+
8
+ This package sits outside `chipgraph.core`: it reasons about chips, tools and file
9
+ layouts, and imports checks/app freely.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+
16
+ from chipgraph.learn.draft import (
17
+ DRAFT_NAMING_RULES_NAME,
18
+ DRAFT_PROFILE_NAME,
19
+ build_naming_rules,
20
+ build_profile_dict,
21
+ dump_naming_rules_yaml,
22
+ dump_profile_yaml,
23
+ dump_yaml,
24
+ write_draft,
25
+ )
26
+ from chipgraph.learn.infer import DEFAULT_THRESHOLD, learn
27
+ from chipgraph.learn.models import (
28
+ Coverage,
29
+ LayoutRule,
30
+ LearnResult,
31
+ NamingRule,
32
+ Observation,
33
+ )
34
+ from chipgraph.learn.try_run import TryReport, try_run
35
+
36
+ _INIT_NAMING_RULES_REL = ".chipgraph/naming.yml"
37
+ """Where `init --from-learn` writes the learned naming rules, referenced as `path:...`."""
38
+
39
+
40
+ class InitFromLearnError(Exception):
41
+ """Raised when `init --from-learn` cannot write (e.g. `.chipgraph.yml` exists)."""
42
+
43
+
44
+ def init_from_learn(
45
+ root: Path, *, threshold: float = DEFAULT_THRESHOLD
46
+ ) -> tuple[Path, Path | None]:
47
+ """Write `.chipgraph.yml` (and a naming-rules file) into `root` from a fresh learn.
48
+
49
+ Refuses to overwrite an existing `.chipgraph.yml`. Returns `(profile_path,
50
+ naming_rules_path_or_None)`.
51
+ """
52
+ profile_path = root / ".chipgraph.yml"
53
+ if profile_path.is_file():
54
+ raise InitFromLearnError(
55
+ f"{profile_path} already exists; refusing to overwrite (remove it first)"
56
+ )
57
+
58
+ result = learn(root, threshold=threshold)
59
+ rules = build_naming_rules(result)
60
+ naming_path: Path | None = None
61
+ naming_ref: str | None = None
62
+ if rules is not None:
63
+ naming_path = root / _INIT_NAMING_RULES_REL
64
+ naming_path.parent.mkdir(parents=True, exist_ok=True)
65
+ naming_path.write_text(dump_naming_rules_yaml(rules), encoding="utf-8")
66
+ naming_ref = _INIT_NAMING_RULES_REL
67
+
68
+ data = build_profile_dict(result, naming_rules_ref=naming_ref)
69
+ header = (
70
+ "# chipgraph project profile written by `chipgraph init --from-learn` (DESIGN.md 8.6).\n"
71
+ "# Review it: each rule was inferred from the repo. Edit freely.\n"
72
+ )
73
+ profile_path.write_text(header + dump_yaml(data), encoding="utf-8")
74
+ return profile_path, naming_path
75
+
76
+
77
+ __all__ = [
78
+ "DEFAULT_THRESHOLD",
79
+ "DRAFT_NAMING_RULES_NAME",
80
+ "DRAFT_PROFILE_NAME",
81
+ "Coverage",
82
+ "InitFromLearnError",
83
+ "LayoutRule",
84
+ "LearnResult",
85
+ "NamingRule",
86
+ "Observation",
87
+ "TryReport",
88
+ "build_naming_rules",
89
+ "build_profile_dict",
90
+ "dump_naming_rules_yaml",
91
+ "dump_profile_yaml",
92
+ "init_from_learn",
93
+ "learn",
94
+ "try_run",
95
+ "write_draft",
96
+ ]
@@ -0,0 +1,187 @@
1
+ """Turn a `LearnResult` into a draft `.chipgraph.yml` profile and a naming-rules file.
2
+
3
+ The draft is what `chipgraph learn --out` writes and what `chipgraph try` / `chipgraph
4
+ init --from-learn` consume. It is deliberately small: the layout templates, a `naming`
5
+ adapter pointing at a written rules file, a `layout` and a `filelist` adapter, the block
6
+ list, and a `paths:` exception per vendored directory. A human reviews it before use
7
+ (DESIGN.md 8.6 V1).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+ from chipgraph.checks._naming_rules import KindRule, NamingRules
18
+ from chipgraph.learn.models import LearnResult
19
+
20
+ DRAFT_PROFILE_NAME = "chipgraph.draft.yml"
21
+ """File name `learn --out` writes the draft profile as (never `.chipgraph.yml`)."""
22
+
23
+ DRAFT_NAMING_RULES_NAME = "chipgraph.draft.naming.yml"
24
+ """File name `learn --out` writes the learned naming rules as."""
25
+
26
+ _NAMING_MESSAGES = {
27
+ "module": "does not match the learned module pattern",
28
+ "port": "does not match the learned port pattern",
29
+ "parameter": "does not match the learned parameter pattern",
30
+ "localparam": "does not match the learned localparam pattern",
31
+ "enum_value": "does not match the learned enum-value pattern",
32
+ "instance": "does not match the learned instance pattern",
33
+ "signal": "does not match the learned signal pattern",
34
+ "memory": "does not match the learned memory pattern",
35
+ "genvar": "does not match the learned genvar pattern",
36
+ }
37
+
38
+
39
+ def build_naming_rules(result: LearnResult) -> NamingRules | None:
40
+ """The `NamingRules` model for the emitted (threshold-meeting) naming patterns.
41
+
42
+ Returns `None` when no pattern met the threshold, so no rules file is written and the
43
+ draft omits the `naming` adapter entirely (an empty rule file would reject everything).
44
+ """
45
+ emitted = result.emitted_naming
46
+ if not emitted:
47
+ return None
48
+ identifiers = {
49
+ rule.kind: KindRule(
50
+ rule=f"learned.{rule.kind}",
51
+ pattern=rule.pattern,
52
+ message=_NAMING_MESSAGES.get(rule.kind, "does not match the learned pattern"),
53
+ )
54
+ for rule in emitted
55
+ }
56
+ return NamingRules(
57
+ document=result.naming_rules_document,
58
+ version="draft",
59
+ identifiers=identifiers,
60
+ )
61
+
62
+
63
+ def build_profile_dict(result: LearnResult, *, naming_rules_ref: str | None) -> dict[str, Any]:
64
+ """The draft profile as a plain dict, ready to dump as YAML.
65
+
66
+ `naming_rules_ref` is the profile-relative path the naming-rules file will live at
67
+ (e.g. `chipgraph.draft.naming.yml` next to the draft, or `.chipgraph/naming.yml` for
68
+ `init --from-learn`); `None` when no naming rule met the threshold.
69
+ """
70
+ layout = {rule.kind: rule.template for rule in result.layout}
71
+ adapters: dict[str, Any] = {}
72
+
73
+ if layout:
74
+ scope = sorted({_scope_glob(rule.template) for rule in result.layout})
75
+ adapters["layout"] = {"use": "layout", "templates": layout, "scope": scope}
76
+
77
+ filelist_tmpl = layout.get("filelist")
78
+ rtl_tmpl = layout.get("rtl")
79
+ # The `filelist` check resolves listed paths relative to the filelist's own directory,
80
+ # so it only fits repos with that style. A root-relative repo (e.g. tinysoc, whose
81
+ # Makefile runs Verilator from the root) would fail every entry; the style is reported
82
+ # as an observation instead, and no `filelist` adapter is emitted.
83
+ if filelist_tmpl is not None and rtl_tmpl is not None and result.filelist_style == "filelist":
84
+ adapters["filelist"] = {
85
+ "use": "filelist",
86
+ "filelist": filelist_tmpl,
87
+ "sources": rtl_tmpl,
88
+ }
89
+
90
+ if naming_rules_ref is not None and rtl_tmpl is not None:
91
+ adapters["naming"] = {
92
+ "use": "naming",
93
+ "rules": f"path:{naming_rules_ref}",
94
+ "scope": [rtl_tmpl],
95
+ }
96
+
97
+ profile: dict[str, Any] = {
98
+ "schema_version": 1,
99
+ "project": result.project,
100
+ "packs": [],
101
+ "layout": layout,
102
+ "adapters": adapters,
103
+ "blocks": {block: {} for block in result.blocks},
104
+ }
105
+
106
+ paths = {
107
+ glob: {"checks": {"naming": "off", "layout": "off"}, "reason": "vendored, learned"}
108
+ for glob in result.vendor_paths
109
+ }
110
+ if paths:
111
+ profile["paths"] = paths
112
+
113
+ return profile
114
+
115
+
116
+ def _scope_glob(template: str) -> str:
117
+ """A scope glob for a layout template: the placeholders become `*`.
118
+
119
+ The `layout` check scopes which files it inspects; a scope that mirrors the template
120
+ keeps the check from complaining about unrelated files (READMEs, scripts).
121
+ """
122
+ return template.replace("{block}", "*").replace("{BLOCK}", "*")
123
+
124
+
125
+ class _NoAliasDumper(yaml.SafeDumper):
126
+ """A safe dumper that writes repeated values out in full, never as `&id001`/`*id001`.
127
+
128
+ The draft is read and edited by people, and YAML anchors make it hard to follow.
129
+ """
130
+
131
+ def ignore_aliases(self, data: object) -> bool:
132
+ return True
133
+
134
+
135
+ def dump_yaml(data: object) -> str:
136
+ """`data` as block-style YAML, sorted keys, no anchors or aliases."""
137
+ return yaml.dump(data, Dumper=_NoAliasDumper, sort_keys=True, default_flow_style=False)
138
+
139
+
140
+ def dump_profile_yaml(result: LearnResult, *, naming_rules_ref: str | None) -> str:
141
+ """The draft profile rendered as a YAML document string."""
142
+ header = (
143
+ "# chipgraph draft profile inferred by `chipgraph learn` (DESIGN.md 8.6 V1).\n"
144
+ "# Review before use: each rule was inferred from the repo with a coverage figure.\n"
145
+ # The directory name only: this file is committed, so no machine-local path.
146
+ f"# Learned from: {Path(result.root).name}/\n"
147
+ )
148
+ body = dump_yaml(build_profile_dict(result, naming_rules_ref=naming_rules_ref))
149
+ return header + body
150
+
151
+
152
+ def dump_naming_rules_yaml(rules: NamingRules) -> str:
153
+ """The learned naming rules rendered as a YAML document string."""
154
+ header = "# chipgraph naming rules inferred by `chipgraph learn`. Review before use.\n"
155
+ body = dump_yaml(rules.model_dump(mode="json"))
156
+ return header + body
157
+
158
+
159
+ def write_draft(result: LearnResult, out_dir: Path) -> tuple[Path, Path | None]:
160
+ """Write the draft profile (and naming-rules file) into `out_dir`.
161
+
162
+ Returns `(profile_path, naming_rules_path_or_None)`. `out_dir` is created if missing.
163
+ """
164
+ out_dir.mkdir(parents=True, exist_ok=True)
165
+ rules = build_naming_rules(result)
166
+ naming_path: Path | None = None
167
+ naming_ref: str | None = None
168
+ if rules is not None:
169
+ naming_path = out_dir / DRAFT_NAMING_RULES_NAME
170
+ naming_path.write_text(dump_naming_rules_yaml(rules), encoding="utf-8")
171
+ naming_ref = DRAFT_NAMING_RULES_NAME
172
+ profile_path = out_dir / DRAFT_PROFILE_NAME
173
+ profile_path.write_text(
174
+ dump_profile_yaml(result, naming_rules_ref=naming_ref), encoding="utf-8"
175
+ )
176
+ return profile_path, naming_path
177
+
178
+
179
+ __all__ = [
180
+ "DRAFT_NAMING_RULES_NAME",
181
+ "DRAFT_PROFILE_NAME",
182
+ "build_naming_rules",
183
+ "build_profile_dict",
184
+ "dump_naming_rules_yaml",
185
+ "dump_profile_yaml",
186
+ "write_draft",
187
+ ]