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.
- chipgraph/__init__.py +7 -0
- chipgraph/adapters/__init__.py +1 -0
- chipgraph/adapters/format/__init__.py +6 -0
- chipgraph/adapters/format/__main__.py +16 -0
- chipgraph/adapters/format/_yaml_lines.py +54 -0
- chipgraph/adapters/format/chip_yaml.py +323 -0
- chipgraph/adapters/format/qsoc_contract.py +246 -0
- chipgraph/adapters/llm/__init__.py +58 -0
- chipgraph/adapters/llm/_budget.py +173 -0
- chipgraph/adapters/llm/_errors.py +50 -0
- chipgraph/adapters/llm/_nda.py +30 -0
- chipgraph/adapters/llm/_retry.py +85 -0
- chipgraph/adapters/llm/anthropic.py +247 -0
- chipgraph/adapters/llm/decide_backend.py +179 -0
- chipgraph/adapters/llm/fake.py +87 -0
- chipgraph/adapters/llm/guard.py +68 -0
- chipgraph/adapters/parser/__init__.py +22 -0
- chipgraph/adapters/parser/cocotb.py +304 -0
- chipgraph/adapters/parser/generic_regex.py +75 -0
- chipgraph/adapters/parser/verible.py +55 -0
- chipgraph/adapters/parser/verilator.py +46 -0
- chipgraph/adapters/review/__init__.py +7 -0
- chipgraph/adapters/review/file.py +123 -0
- chipgraph/adapters/review/github.py +886 -0
- chipgraph/adapters/runner/__init__.py +7 -0
- chipgraph/adapters/runner/local.py +122 -0
- chipgraph/adapters/runtime/__init__.py +5 -0
- chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
- chipgraph/adapters/runtime/claude_code/agents.py +158 -0
- chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
- chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
- chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
- chipgraph/adapters/runtime/claude_code/loop.py +342 -0
- chipgraph/adapters/runtime/claude_code/previous.py +419 -0
- chipgraph/adapters/runtime/claude_code/review.py +492 -0
- chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
- chipgraph/adapters/runtime/claude_code/service.py +2285 -0
- chipgraph/adapters/tool/__init__.py +7 -0
- chipgraph/adapters/tool/cmd.py +229 -0
- chipgraph/adapters/tool/edalize.py +773 -0
- chipgraph/adapters/tool/filelist.py +259 -0
- chipgraph/adapters/tool/pyslang.py +485 -0
- chipgraph/adapters/vcs/__init__.py +1 -0
- chipgraph/adapters/vcs/git.py +521 -0
- chipgraph/app/__init__.py +10 -0
- chipgraph/app/baseline.py +339 -0
- chipgraph/app/build.py +332 -0
- chipgraph/app/checks.py +202 -0
- chipgraph/app/context.py +191 -0
- chipgraph/app/errors.py +11 -0
- chipgraph/app/executors.py +79 -0
- chipgraph/app/findings.py +203 -0
- chipgraph/app/ingest.py +523 -0
- chipgraph/checks/__init__.py +52 -0
- chipgraph/checks/_cdc_analyze.py +272 -0
- chipgraph/checks/_cdc_elaborate.py +455 -0
- chipgraph/checks/_common.py +268 -0
- chipgraph/checks/_cross.py +96 -0
- chipgraph/checks/_model.py +64 -0
- chipgraph/checks/_naming_pyslang.py +235 -0
- chipgraph/checks/_naming_rules.py +127 -0
- chipgraph/checks/_xref_syntax.py +260 -0
- chipgraph/checks/cdc_struct.py +235 -0
- chipgraph/checks/connect.py +353 -0
- chipgraph/checks/cross_chip.py +454 -0
- chipgraph/checks/duplicate.py +153 -0
- chipgraph/checks/filelist.py +323 -0
- chipgraph/checks/format.py +268 -0
- chipgraph/checks/generated.py +151 -0
- chipgraph/checks/hardcode.py +233 -0
- chipgraph/checks/layout.py +155 -0
- chipgraph/checks/naming.py +311 -0
- chipgraph/checks/plan_check.py +901 -0
- chipgraph/checks/ports_diff.py +203 -0
- chipgraph/checks/spec_schema.py +277 -0
- chipgraph/checks/style.py +910 -0
- chipgraph/checks/tb_static.py +599 -0
- chipgraph/checks/trace.py +224 -0
- chipgraph/checks/vendor_guard.py +369 -0
- chipgraph/checks/wrap_check.py +486 -0
- chipgraph/cli/__init__.py +1688 -0
- chipgraph/cli/next_steps.py +137 -0
- chipgraph/core/__init__.py +1 -0
- chipgraph/core/config/__init__.py +93 -0
- chipgraph/core/config/defaults.py +14 -0
- chipgraph/core/config/errors.py +32 -0
- chipgraph/core/config/loader.py +766 -0
- chipgraph/core/config/models.py +845 -0
- chipgraph/core/config/templates.py +340 -0
- chipgraph/core/contracts/__init__.py +75 -0
- chipgraph/core/contracts/_paths.py +26 -0
- chipgraph/core/contracts/agent.py +49 -0
- chipgraph/core/contracts/artifact.py +68 -0
- chipgraph/core/contracts/check.py +76 -0
- chipgraph/core/contracts/event.py +70 -0
- chipgraph/core/contracts/export.py +128 -0
- chipgraph/core/contracts/finding.py +171 -0
- chipgraph/core/contracts/gate.py +50 -0
- chipgraph/core/contracts/rule.py +177 -0
- chipgraph/core/contracts/types.py +45 -0
- chipgraph/core/engine/__init__.py +70 -0
- chipgraph/core/engine/agent_rule.py +1362 -0
- chipgraph/core/engine/baseline.py +283 -0
- chipgraph/core/engine/decide.py +462 -0
- chipgraph/core/engine/fanout.py +1358 -0
- chipgraph/core/engine/gate.py +170 -0
- chipgraph/core/engine/graph.py +647 -0
- chipgraph/core/engine/records.py +60 -0
- chipgraph/core/engine/rules.py +92 -0
- chipgraph/core/engine/scheduler.py +637 -0
- chipgraph/core/engine/select.py +1239 -0
- chipgraph/core/engine/targets.py +342 -0
- chipgraph/core/model/__init__.py +98 -0
- chipgraph/core/model/entities.py +244 -0
- chipgraph/core/model/export.py +16 -0
- chipgraph/core/model/ingest.py +693 -0
- chipgraph/core/model/json_value.py +4 -0
- chipgraph/core/model/keys.py +95 -0
- chipgraph/core/model/model.py +153 -0
- chipgraph/core/model/provenance.py +27 -0
- chipgraph/core/model/query.py +583 -0
- chipgraph/core/model/registry.py +80 -0
- chipgraph/core/model/relations.py +61 -0
- chipgraph/core/model/store.py +279 -0
- chipgraph/core/plugin_api/__init__.py +81 -0
- chipgraph/core/plugin_api/local.py +167 -0
- chipgraph/core/plugin_api/pack.py +159 -0
- chipgraph/core/plugin_api/protocols.py +177 -0
- chipgraph/core/plugin_api/registry.py +111 -0
- chipgraph/core/plugin_api/types.py +94 -0
- chipgraph/core/runtime/__init__.py +41 -0
- chipgraph/core/runtime/executor.py +79 -0
- chipgraph/core/runtime/queue.py +565 -0
- chipgraph/core/runtime/roles/__init__.py +91 -0
- chipgraph/core/runtime/roles/data/author.md +41 -0
- chipgraph/core/runtime/roles/data/critic.md +56 -0
- chipgraph/core/runtime/roles/data/planner.md +38 -0
- chipgraph/core/runtime/roles/data/researcher.md +37 -0
- chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
- chipgraph/core/runtime/roles/data/tb-author.md +48 -0
- chipgraph/core/runtime/roles/data/triage.md +34 -0
- chipgraph/core/runtime/roles/policy.py +299 -0
- chipgraph/core/runtime/roles/registry.py +52 -0
- chipgraph/core/runtime/roles/review.py +275 -0
- chipgraph/core/runtime/roles/schema.py +82 -0
- chipgraph/core/runtime/roles/skills.py +134 -0
- chipgraph/core/runtime/roles/spec.py +232 -0
- chipgraph/core/state/__init__.py +1 -0
- chipgraph/core/state/artifacts.py +156 -0
- chipgraph/core/state/backend.py +117 -0
- chipgraph/core/state/findings.py +151 -0
- chipgraph/core/state/handoff.py +411 -0
- chipgraph/core/state/idempotency.py +59 -0
- chipgraph/core/state/journal.py +238 -0
- chipgraph/core/state/layout.py +82 -0
- chipgraph/core/state/lock.py +195 -0
- chipgraph/core/state/trace.py +225 -0
- chipgraph/learn/__init__.py +96 -0
- chipgraph/learn/draft.py +187 -0
- chipgraph/learn/infer.py +580 -0
- chipgraph/learn/models.py +145 -0
- chipgraph/learn/try_run.py +228 -0
- chipgraph/mcp/__init__.py +12 -0
- chipgraph/mcp/model_tools.py +167 -0
- chipgraph/mcp/server.py +787 -0
- chipgraph/orgs/qnsc/naming-v1.yml +113 -0
- chipgraph/orgs/qnsc/profile.yml +5 -0
- chipgraph/packs/__init__.py +8 -0
- chipgraph/packs/assist/__init__.py +16 -0
- chipgraph/packs/assist/ask/__init__.py +58 -0
- chipgraph/packs/assist/ask/_db.py +57 -0
- chipgraph/packs/assist/ask/_project.py +230 -0
- chipgraph/packs/assist/ask/answer.py +266 -0
- chipgraph/packs/assist/ask/check.py +182 -0
- chipgraph/packs/assist/ask/contract.py +115 -0
- chipgraph/packs/assist/ask/documents.py +157 -0
- chipgraph/packs/assist/ask/fts.py +80 -0
- chipgraph/packs/assist/ask/retrieve.py +331 -0
- chipgraph/packs/assist/audit/__init__.py +383 -0
- chipgraph/packs/assist/pack.yml +13 -0
- chipgraph/packs/assist/triage/__init__.py +64 -0
- chipgraph/packs/assist/triage/contract.py +94 -0
- chipgraph/packs/assist/triage/parse.py +371 -0
- chipgraph/packs/assist/triage/question.py +384 -0
- chipgraph/packs/assist/triage/report.py +160 -0
- chipgraph/packs/assist/triage/rules.py +447 -0
- chipgraph/packs/assist/triage/run.py +224 -0
- chipgraph/packs/digital_rtl/__init__.py +19 -0
- chipgraph/packs/digital_rtl/layout.py +144 -0
- chipgraph/packs/digital_rtl/pack.yml +25 -0
- chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
- chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
- chipgraph/packs/digital_rtl/plan/context.py +82 -0
- chipgraph/packs/digital_rtl/plan/expand.py +94 -0
- chipgraph/packs/digital_rtl/plan/model.py +150 -0
- chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
- chipgraph/packs/digital_rtl/plan/show.py +176 -0
- chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
- chipgraph/packs/digital_rtl/pr/body.py +165 -0
- chipgraph/packs/digital_rtl/pr/gate.py +110 -0
- chipgraph/packs/digital_rtl/pr/open.py +397 -0
- chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
- chipgraph/packs/digital_rtl/rtl/context.py +272 -0
- chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
- chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
- chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
- chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
- chipgraph/packs/digital_rtl/rules/review.yml +31 -0
- chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
- chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
- chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
- chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
- chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
- chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
- chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
- chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
- chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
- chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
- chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
- chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
- chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
- chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
- chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
- chipgraph/packs/dv/__init__.py +15 -0
- chipgraph/packs/dv/declaration.py +392 -0
- chipgraph/packs/dv/interface.py +596 -0
- chipgraph/packs/dv/pack.yml +14 -0
- chipgraph/packs/dv/rules/tb_module.yml +42 -0
- chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
- chipgraph/packs/spec_core/__init__.py +9 -0
- chipgraph/packs/spec_core/extract/__init__.py +12 -0
- chipgraph/packs/spec_core/extract/_markdown.py +371 -0
- chipgraph/packs/spec_core/extract/_text.py +116 -0
- chipgraph/packs/spec_core/extract/mas.py +975 -0
- chipgraph/packs/spec_core/gen/__init__.py +11 -0
- chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
- chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
- chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
- chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
- chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
- chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
- chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
- chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
- chipgraph/packs/spec_core/pack.yml +6 -0
- chipgraph/py.typed +0 -0
- chipgraph-0.1.0.dist-info/METADATA +53 -0
- chipgraph-0.1.0.dist-info/RECORD +252 -0
- chipgraph-0.1.0.dist-info/WHEEL +4 -0
- chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
- 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
|
+
]
|
chipgraph/learn/draft.py
ADDED
|
@@ -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
|
+
]
|