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,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
|