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,145 @@
1
+ """Result models for `chipgraph learn` (DESIGN.md 8.6 V1).
2
+
3
+ `learn` reads an existing repository and infers the conventions a `.chipgraph.yml`
4
+ declares: where files live (`layout`), how identifiers are named (`naming`), the common
5
+ file header, the filelist style, and the document template. It reports each inference as
6
+ evidence with a coverage statistic ("97% of ports match `^(i|o|io)_`") rather than as a
7
+ silent guess, so a human reviews the draft before it is used.
8
+
9
+ These models are the machine-readable form of that report. They live outside
10
+ `chipgraph.core` on purpose: `learn` reasons about chips, tools and file layouts, which
11
+ `core` must not.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Literal
17
+
18
+ from pydantic import BaseModel, ConfigDict, Field
19
+
20
+ Kind = Literal[
21
+ "module",
22
+ "port",
23
+ "parameter",
24
+ "localparam",
25
+ "enum_value",
26
+ "instance",
27
+ "signal",
28
+ "memory",
29
+ "genvar",
30
+ ]
31
+ """The identifier kinds `learn` infers a naming pattern for (from `_naming_pyslang`)."""
32
+
33
+
34
+ class _Model(BaseModel):
35
+ model_config = ConfigDict(extra="forbid", frozen=True)
36
+
37
+
38
+ class Coverage(_Model):
39
+ """How many observed items an inferred pattern matches: `matched` of `total`."""
40
+
41
+ matched: int = Field(ge=0, description="Number of observed items the pattern matches.")
42
+ total: int = Field(ge=0, description="Total number of observed items of this kind.")
43
+
44
+ @property
45
+ def ratio(self) -> float:
46
+ """`matched / total`, or `1.0` when there is nothing to cover."""
47
+ return 1.0 if self.total == 0 else self.matched / self.total
48
+
49
+ @property
50
+ def percent(self) -> int:
51
+ """Coverage as a whole-number percentage, e.g. `97`."""
52
+ return round(self.ratio * 100)
53
+
54
+
55
+ class LayoutRule(_Model):
56
+ """An inferred `layout` entry: an artifact kind and its path template, with coverage."""
57
+
58
+ kind: str = Field(description="The artifact kind, e.g. 'rtl', 'filelist', 'spec', 'tb'.")
59
+ template: str = Field(description="The inferred path template, e.g. 'rtl/{block}.sv'.")
60
+ coverage: Coverage = Field(description="Files of this kind the template matches.")
61
+ examples: tuple[str, ...] = Field(
62
+ default=(), description="A few example paths the template was inferred from."
63
+ )
64
+
65
+
66
+ class NamingRule(_Model):
67
+ """An inferred naming pattern for one identifier kind, with coverage and evidence."""
68
+
69
+ kind: Kind = Field(description="The identifier kind this rule constrains.")
70
+ pattern: str = Field(description="A regex the identifier must fully match to pass.")
71
+ coverage: Coverage = Field(description="Declared identifiers of this kind the pattern matches.")
72
+ emitted: bool = Field(
73
+ description="Whether coverage met the threshold, so this is a rule (not an observation)."
74
+ )
75
+ description: str = Field(
76
+ description="Human-readable summary, e.g. '97% of ports match `^(i|o|io)_`'."
77
+ )
78
+ examples: tuple[str, ...] = Field(
79
+ default=(), description="A few example identifiers the pattern was inferred from."
80
+ )
81
+
82
+
83
+ class Observation(_Model):
84
+ """A learned fact reported for the reviewer, not emitted as a check (e.g. the header)."""
85
+
86
+ topic: str = Field(description="What was observed, e.g. 'header', 'filelist', 'doc'.")
87
+ summary: str = Field(description="A human-readable one-line summary of the observation.")
88
+ coverage: Coverage | None = Field(
89
+ default=None, description="Coverage, when the observation is a ratio."
90
+ )
91
+ detail: dict[str, Any] = Field(
92
+ default_factory=dict, description="Structured detail, e.g. the header text or headings."
93
+ )
94
+
95
+
96
+ class LearnResult(_Model):
97
+ """The full result of `learn`: a draft profile plus the evidence behind it (DESIGN 8.6)."""
98
+
99
+ schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
100
+ root: str = Field(description="The repository root that was learned, as given.")
101
+ project: str = Field(description="The inferred project name (the root directory name).")
102
+ threshold: float = Field(
103
+ description="Minimum coverage for a naming pattern to be emitted as a rule."
104
+ )
105
+ blocks: tuple[str, ...] = Field(
106
+ default=(), description="Block names inferred from the repeated directory level."
107
+ )
108
+ layout: tuple[LayoutRule, ...] = Field(
109
+ default=(), description="Inferred layout templates, one per artifact kind."
110
+ )
111
+ naming: tuple[NamingRule, ...] = Field(
112
+ default=(), description="Inferred naming patterns, per identifier kind."
113
+ )
114
+ observations: tuple[Observation, ...] = Field(
115
+ default=(), description="Learned facts reported for review (header, filelist, doc)."
116
+ )
117
+ vendor_paths: tuple[str, ...] = Field(
118
+ default=(), description="Vendor/third-party path globs to exempt from checks."
119
+ )
120
+ filelist_style: Literal["filelist", "root", "none"] = Field(
121
+ default="none",
122
+ description=(
123
+ "How filelist paths resolve: 'filelist' (relative to the filelist's own dir, "
124
+ "which the `filelist` check enforces), 'root' (relative to the repo root, which "
125
+ "it cannot yet), or 'none' (no filelists found)."
126
+ ),
127
+ )
128
+ naming_rules_document: str = Field(
129
+ default="learned", description="The `document` field written into the naming-rules file."
130
+ )
131
+
132
+ @property
133
+ def emitted_naming(self) -> tuple[NamingRule, ...]:
134
+ """The naming rules whose coverage met the threshold."""
135
+ return tuple(r for r in self.naming if r.emitted)
136
+
137
+
138
+ __all__ = [
139
+ "Coverage",
140
+ "Kind",
141
+ "LayoutRule",
142
+ "LearnResult",
143
+ "NamingRule",
144
+ "Observation",
145
+ ]
@@ -0,0 +1,228 @@
1
+ """`chipgraph try`: run chipgraph read-only against a repo, writing nothing into it.
2
+
3
+ `try` lets a user evaluate chipgraph on a repository before committing a `.chipgraph.yml`
4
+ (DESIGN.md 8.4). It infers a draft profile (or reads one), then runs only *read* commands:
5
+ `ingest` into a temporary state directory, `check`, and `audit` when that pack's API is
6
+ importable. It guarantees three things the tests pin:
7
+
8
+ - the repository is left byte-identical (its `.chipgraph/` is never created): every write
9
+ goes to a private working copy under a temp directory, so the repo is only ever read;
10
+ - local plugins are disabled (`CHIPGRAPH_LOCAL_PLUGINS=0`): an untrusted repo must not run
11
+ its own code during a `try`;
12
+ - it runs the `assist` pack's audit over the temporary state (no second ingest).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import os
19
+ import shutil
20
+ import tempfile
21
+ from collections.abc import Iterator
22
+ from contextlib import contextmanager
23
+ from dataclasses import dataclass, field
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ import yaml
28
+
29
+ from chipgraph.app.checks import ProfileCheckRunner
30
+ from chipgraph.app.context import AppContext
31
+ from chipgraph.core.contracts import ArtifactRef, CheckResult, RuleInstance
32
+ from chipgraph.learn.draft import (
33
+ build_naming_rules,
34
+ build_profile_dict,
35
+ dump_naming_rules_yaml,
36
+ dump_yaml,
37
+ )
38
+ from chipgraph.learn.infer import DEFAULT_THRESHOLD, learn
39
+ from chipgraph.learn.models import LearnResult
40
+
41
+ _LOCAL_PLUGINS_ENV = "CHIPGRAPH_LOCAL_PLUGINS"
42
+ _NAMING_RULES_NAME = "chipgraph.draft.naming.yml"
43
+ _SKIP_COPY = frozenset({".git", ".chipgraph"})
44
+
45
+
46
+ @dataclass(slots=True)
47
+ class TryReport:
48
+ """What `try` did: the draft it used, the ingest stats, check results, audit status."""
49
+
50
+ result: LearnResult
51
+ ingest_ok: bool
52
+ ingest_summary: str
53
+ checks: list[tuple[str, str | None, CheckResult]] = field(default_factory=list)
54
+ audit_available: bool = False
55
+ audit_summary: str = ""
56
+
57
+ @property
58
+ def check_files_with_issues(self) -> int:
59
+ """Number of distinct files any check reported an issue for."""
60
+ files: set[str] = set()
61
+ for _cid, _blk, result in self.checks:
62
+ for issue in result.issues:
63
+ if issue.file:
64
+ files.add(issue.file)
65
+ return len(files)
66
+
67
+
68
+ def try_run(
69
+ repo_root: Path,
70
+ *,
71
+ profile_path: Path | None = None,
72
+ threshold: float = DEFAULT_THRESHOLD,
73
+ ) -> TryReport:
74
+ """Learn (or read a draft), then run ingest/check/audit read-only against `repo_root`.
75
+
76
+ `profile_path`, when given, is a draft profile written by `learn --out`; otherwise the
77
+ repo is learned fresh. Nothing is ever written into `repo_root`: a private working copy
78
+ is made under a temp dir, and all state and the model DB land there.
79
+ """
80
+ repo_root = repo_root.resolve()
81
+ result = learn(repo_root, threshold=threshold)
82
+
83
+ with _local_plugins_disabled(), tempfile.TemporaryDirectory(prefix="chipgraph-try-") as tmp:
84
+ work = Path(tmp) / "repo"
85
+ _mirror_repo(repo_root, work)
86
+ _place_profile(work, result, profile_path)
87
+ ctx = AppContext.load(work)
88
+ report = _run_readonly(ctx, result)
89
+ return report
90
+
91
+
92
+ # --------------------------------------------------------------------------------------
93
+ # read-only run against the working copy
94
+ # --------------------------------------------------------------------------------------
95
+
96
+
97
+ def _run_readonly(ctx: AppContext, result: LearnResult) -> TryReport:
98
+ ingest_ok, ingest_summary = _try_ingest(ctx)
99
+ checks = _run_checks(ctx)
100
+ audit_available, audit_summary = _try_audit(ctx)
101
+ return TryReport(
102
+ result=result,
103
+ ingest_ok=ingest_ok,
104
+ ingest_summary=ingest_summary,
105
+ checks=checks,
106
+ audit_available=audit_available,
107
+ audit_summary=audit_summary,
108
+ )
109
+
110
+
111
+ def _try_ingest(ctx: AppContext) -> tuple[bool, str]:
112
+ from chipgraph.app.ingest import run_ingest
113
+
114
+ try:
115
+ report = run_ingest(ctx)
116
+ except Exception as exc: # a draft may not ingest cleanly; try must not crash
117
+ return False, f"ingest failed: {exc}"
118
+ stats = report.result.stats
119
+ return (
120
+ not report.has_error,
121
+ f"inputs={stats.inputs} entities={stats.entities} relations={stats.relations}",
122
+ )
123
+
124
+
125
+ def _run_checks(ctx: AppContext) -> list[tuple[str, str | None, CheckResult]]:
126
+ resolved = ctx.require_profile()
127
+ runner = ProfileCheckRunner(ctx)
128
+ check_ids = sorted(resolved.profile.adapters)
129
+ raw_blocks = sorted(resolved.profile.blocks)
130
+ out: list[tuple[str, str | None, CheckResult]] = []
131
+
132
+ async def _run() -> None:
133
+ for cid in check_ids:
134
+ blocks: list[str | None] = (
135
+ list(raw_blocks) if runner.runs_per_block(cid) and raw_blocks else [None]
136
+ )
137
+ for block in blocks:
138
+ instance = _instance(cid, block)
139
+ result = await runner.run(cid, instance)
140
+ out.append((cid, block, result))
141
+
142
+ asyncio.run(_run())
143
+ return out
144
+
145
+
146
+ def _instance(check_id: str, block: str | None) -> RuleInstance:
147
+ params = {"block": block} if block is not None else {}
148
+ rule_id = "chipgraph/try"
149
+ return RuleInstance(
150
+ rule_id=rule_id,
151
+ params=params,
152
+ outputs=(ArtifactRef(kind="report", path=f".chipgraph/tmp/try-{check_id}.json"),),
153
+ instance_id=RuleInstance.make_id(rule_id, params),
154
+ )
155
+
156
+
157
+ def _try_audit(ctx: AppContext) -> tuple[bool, str]:
158
+ """Run the `assist` pack's audit over what `try` already ingested; its one-line summary.
159
+
160
+ Ingest has already run into the temporary state, so the audit does not repeat it. An
161
+ audit that fails is reported, not raised: `try` is a first look at a repo.
162
+ """
163
+ from chipgraph.packs.assist import run_audit
164
+
165
+ try:
166
+ report = run_audit(ctx, ingest=False)
167
+ except Exception as exc:
168
+ return True, f"audit ran with errors: {exc}"
169
+ return True, f"audit: {report.summary_line()}"
170
+
171
+
172
+ # --------------------------------------------------------------------------------------
173
+ # working copy and profile placement
174
+ # --------------------------------------------------------------------------------------
175
+
176
+
177
+ def _mirror_repo(src: Path, dest: Path) -> None:
178
+ """Copy `src` into `dest`, skipping `.git` and any existing `.chipgraph/` state.
179
+
180
+ `try` reads the repo and writes only into this copy, so the real repo is untouched.
181
+ """
182
+ shutil.copytree(
183
+ src,
184
+ dest,
185
+ symlinks=True,
186
+ ignore=shutil.ignore_patterns(*_SKIP_COPY),
187
+ ignore_dangling_symlinks=True,
188
+ )
189
+
190
+
191
+ def _place_profile(work: Path, result: LearnResult, profile_path: Path | None) -> None:
192
+ """Write the profile (draft or given) and its naming rules into the working copy."""
193
+ if profile_path is not None:
194
+ data = yaml.safe_load(profile_path.read_text(encoding="utf-8")) or {}
195
+ rules_src = profile_path.parent / _NAMING_RULES_NAME
196
+ if rules_src.is_file():
197
+ (work / _NAMING_RULES_NAME).write_text(
198
+ rules_src.read_text(encoding="utf-8"), encoding="utf-8"
199
+ )
200
+ else:
201
+ data = _draft_for_work(work, result)
202
+ (work / ".chipgraph.yml").write_text(dump_yaml(data), encoding="utf-8")
203
+
204
+
205
+ def _draft_for_work(work: Path, result: LearnResult) -> dict[str, Any]:
206
+ rules = build_naming_rules(result)
207
+ naming_ref: str | None = None
208
+ if rules is not None:
209
+ (work / _NAMING_RULES_NAME).write_text(dump_naming_rules_yaml(rules), encoding="utf-8")
210
+ naming_ref = _NAMING_RULES_NAME
211
+ return build_profile_dict(result, naming_rules_ref=naming_ref)
212
+
213
+
214
+ @contextmanager
215
+ def _local_plugins_disabled() -> Iterator[None]:
216
+ """Force `CHIPGRAPH_LOCAL_PLUGINS=0` for the duration, restoring the prior value."""
217
+ prior = os.environ.get(_LOCAL_PLUGINS_ENV)
218
+ os.environ[_LOCAL_PLUGINS_ENV] = "0"
219
+ try:
220
+ yield
221
+ finally:
222
+ if prior is None:
223
+ os.environ.pop(_LOCAL_PLUGINS_ENV, None)
224
+ else:
225
+ os.environ[_LOCAL_PLUGINS_ENV] = prior
226
+
227
+
228
+ __all__ = ["TryReport", "try_run"]
@@ -0,0 +1,12 @@
1
+ """The MCP server (task M0-14): exposes `status`, `build`, `check`, `approve` and
2
+ `config_show` as MCP tools over stdio, for Claude Code or any other MCP client
3
+ (DESIGN.md, section on the MCP server; docs/DECISIONS.md D35).
4
+
5
+ This package builds on `chipgraph.app` and `chipgraph.core`, never the other way
6
+ around: core must not import `chipgraph.mcp` (see `pyproject.toml`'s import-linter
7
+ contract).
8
+ """
9
+
10
+ from chipgraph.mcp.server import build_server, run_stdio
11
+
12
+ __all__ = ["build_server", "run_stdio"]
@@ -0,0 +1,167 @@
1
+ """MCP tools for the Design Model query API (M1-02).
2
+
3
+ Each tool opens the project's model db at `default_model_db_path(layout)`. If the db
4
+ does not exist, raises a tool error "no Design Model yet: run `chipgraph ingest`".
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from chipgraph.app.context import AppContext
12
+ from chipgraph.core.model import (
13
+ ModelQuery,
14
+ ModelStore,
15
+ QueryError,
16
+ default_model_db_path,
17
+ )
18
+
19
+
20
+ async def model_block(ctx: AppContext, name: str) -> dict[str, Any]:
21
+ """Retrieve a block and what it contains."""
22
+ db_path = default_model_db_path(ctx.layout.root)
23
+ if not db_path.is_file():
24
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
25
+
26
+ store = ModelStore(db_path)
27
+ model = store.read()
28
+ query = ModelQuery(model, store)
29
+
30
+ try:
31
+ result = query.block(name)
32
+ return result.model_dump(mode="json")
33
+ except QueryError as exc:
34
+ raise ValueError(str(exc)) from exc
35
+
36
+
37
+ async def model_module(ctx: AppContext, name: str) -> dict[str, Any]:
38
+ """Retrieve a module, its ports, parameters, instances, and who instantiates it."""
39
+ db_path = default_model_db_path(ctx.layout.root)
40
+ if not db_path.is_file():
41
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
42
+
43
+ store = ModelStore(db_path)
44
+ model = store.read()
45
+ query = ModelQuery(model, store)
46
+
47
+ try:
48
+ result = query.module(name)
49
+ return result.model_dump(mode="json")
50
+ except QueryError as exc:
51
+ raise ValueError(str(exc)) from exc
52
+
53
+
54
+ async def model_find(
55
+ ctx: AppContext,
56
+ kind: str | None = None,
57
+ name: str | None = None,
58
+ limit: int = 100,
59
+ ) -> dict[str, Any]:
60
+ """Find entities by kind, name (glob via fnmatch)."""
61
+ db_path = default_model_db_path(ctx.layout.root)
62
+ if not db_path.is_file():
63
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
64
+
65
+ store = ModelStore(db_path)
66
+ model = store.read()
67
+ query = ModelQuery(model, store)
68
+
69
+ try:
70
+ results = query.find(kind=kind, name=name, limit=limit)
71
+ return {"results": [r.model_dump(mode="json") for r in results]}
72
+ except QueryError as exc:
73
+ raise ValueError(str(exc)) from exc
74
+
75
+
76
+ async def model_trace(ctx: AppContext, key: str) -> dict[str, Any]:
77
+ """Trace a requirement or entity: show requirements and links.
78
+
79
+ For requirement entities: shows implements, verifies, derives_from, and flags.
80
+ For other entities: shows requirements that point to them.
81
+ """
82
+ db_path = default_model_db_path(ctx.layout.root)
83
+ if not db_path.is_file():
84
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
85
+
86
+ store = ModelStore(db_path)
87
+ model = store.read()
88
+ query = ModelQuery(model, store)
89
+
90
+ try:
91
+ result = query.trace(key)
92
+ return result.model_dump(mode="json")
93
+ except QueryError as exc:
94
+ raise ValueError(str(exc)) from exc
95
+
96
+
97
+ async def model_impact(ctx: AppContext, key: str, max_depth: int = 5) -> dict[str, Any]:
98
+ """Compute downstream impact from a change at `key`.
99
+
100
+ Traverses `contains`, `instantiates`, `connects`, `derives_from`, `implements`,
101
+ `verifies` relations downstream, up to `max_depth` levels.
102
+ """
103
+ db_path = default_model_db_path(ctx.layout.root)
104
+ if not db_path.is_file():
105
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
106
+
107
+ store = ModelStore(db_path)
108
+ model = store.read()
109
+ query = ModelQuery(model, store)
110
+
111
+ try:
112
+ result = query.impact(key, max_depth=max_depth)
113
+ return result.model_dump(mode="json")
114
+ except QueryError as exc:
115
+ raise ValueError(str(exc)) from exc
116
+
117
+
118
+ async def model_neighbors(
119
+ ctx: AppContext,
120
+ key: str,
121
+ kinds: list[str] | None = None,
122
+ relation: str | None = None,
123
+ direction: str = "out",
124
+ ) -> dict[str, Any]:
125
+ """Find adjacent entities (one hop) in the specified direction.
126
+
127
+ Args:
128
+ key: The entity key.
129
+ kinds: If given, filter destinations by these entity kinds.
130
+ relation: If given, filter relations by this kind.
131
+ direction: "out" (forward), "in" (backward), or "both" (both).
132
+ """
133
+ db_path = default_model_db_path(ctx.layout.root)
134
+ if not db_path.is_file():
135
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
136
+
137
+ store = ModelStore(db_path)
138
+ model = store.read()
139
+ query = ModelQuery(model, store)
140
+
141
+ try:
142
+ result = query.neighbors(key, kinds=kinds, relation=relation, direction=direction)
143
+ return result.model_dump(mode="json")
144
+ except QueryError as exc:
145
+ raise ValueError(str(exc)) from exc
146
+
147
+
148
+ async def model_search(
149
+ ctx: AppContext,
150
+ text: str,
151
+ kinds: list[str] | None = None,
152
+ limit: int = 20,
153
+ ) -> dict[str, Any]:
154
+ """Full-text search over entities and documents."""
155
+ db_path = default_model_db_path(ctx.layout.root)
156
+ if not db_path.is_file():
157
+ raise ValueError("no Design Model yet: run `chipgraph ingest`")
158
+
159
+ store = ModelStore(db_path)
160
+ model = store.read()
161
+ query = ModelQuery(model, store)
162
+
163
+ try:
164
+ results = query.search(text, kinds=kinds, limit=limit)
165
+ return {"results": [r.model_dump(mode="json") for r in results]}
166
+ except QueryError as exc:
167
+ raise ValueError(str(exc)) from exc