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,11 @@
|
|
|
1
|
+
"""Deterministic generators for the ``spec-core`` pack.
|
|
2
|
+
|
|
3
|
+
Currently the diagram generator,
|
|
4
|
+
:class:`~chipgraph.packs.spec_core.gen.diagram.DiagramGenerator`, which renders block,
|
|
5
|
+
memory-map, interrupt-map and clock/reset-tree figures from the Design Model as SVG and
|
|
6
|
+
drawio. Figures are computed from the model, never drawn by an AI (DESIGN.md 4.6).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from chipgraph.packs.spec_core.gen.diagram import DiagramGenerator, render
|
|
10
|
+
|
|
11
|
+
__all__ = ["DiagramGenerator", "render"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""The ``agents-md`` generator (pack ``spec-core``): the chipgraph part of an `AGENTS.md`.
|
|
2
|
+
|
|
3
|
+
Optional and deterministic, no AI (DESIGN.md 4.7, DECISIONS D18/D31). From the resolved
|
|
4
|
+
profile and the organization rules it extends, it writes the project's conventions for
|
|
5
|
+
any coding agent (Codex, Cursor, Claude Code ...): blocks and instances, where files go,
|
|
6
|
+
naming rules, the checks to run before a PR, requirement IDs, path exceptions, data labels
|
|
7
|
+
and generated files.
|
|
8
|
+
|
|
9
|
+
chipgraph manages only the text between ``<!-- chipgraph:begin -->`` and
|
|
10
|
+
``<!-- chipgraph:end -->``; the rest of the file is the team's and is never changed.
|
|
11
|
+
|
|
12
|
+
Public API:
|
|
13
|
+
|
|
14
|
+
- :func:`render_agents_md` — the managed block's content (without the markers).
|
|
15
|
+
Deterministic: sections sorted, no dates, no absolute paths, ``\\n`` line endings.
|
|
16
|
+
- :func:`update_agents_md` — put a block into an existing file's managed part (or append
|
|
17
|
+
it, or return it alone when there is no file); malformed markers raise
|
|
18
|
+
:class:`AgentsMdError`.
|
|
19
|
+
- :func:`agents_md_content` — both in one: the full file content to write.
|
|
20
|
+
|
|
21
|
+
The block is rendered from the Jinja2 template ``agents_md.md.j2`` through
|
|
22
|
+
:class:`~chipgraph.core.config.templates.TemplateSet` (DESIGN 8.6 V2), so a project can
|
|
23
|
+
replace one section without copying the file: ``templates.override`` in `.chipgraph.yml`
|
|
24
|
+
maps a block name to a file holding that one ``{% block %}``, e.g.
|
|
25
|
+
``templates: { override: { agents_md_checks: templates/agents_checks.j2 } }``. Block
|
|
26
|
+
names: ``agents_md_intro``, ``agents_md_project``, ``agents_md_layout``,
|
|
27
|
+
``agents_md_naming``, ``agents_md_checks``, ``agents_md_requirements``,
|
|
28
|
+
``agents_md_paths``, ``agents_md_data``, ``agents_md_generated``. An override may use the
|
|
29
|
+
same context as the pack template, plus the helpers ``code(s, cell=False)`` (a Markdown
|
|
30
|
+
code span) and ``text(s)`` (one line of text, safe in a table cell). Only overrides named
|
|
31
|
+
``agents_md.md.j2`` or starting with ``agents_md_`` are read here.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import re
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
from chipgraph.core.config import ResolvedProfile
|
|
40
|
+
from chipgraph.core.config.templates import TemplateLayer, TemplateSet, TemplateSetError
|
|
41
|
+
from chipgraph.packs.spec_core.gen.agents_md._context import build_context
|
|
42
|
+
from chipgraph.packs.spec_core.gen.agents_md._markers import (
|
|
43
|
+
BEGIN_MARKER,
|
|
44
|
+
END_MARKER,
|
|
45
|
+
AgentsMdError,
|
|
46
|
+
split_managed,
|
|
47
|
+
update_agents_md,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
TEMPLATE_NAME = "agents_md.md.j2"
|
|
51
|
+
"""The pack template's file name (and the key of a whole-file override)."""
|
|
52
|
+
|
|
53
|
+
BLOCK_PREFIX = "agents_md_"
|
|
54
|
+
"""Every block of the template is named with this prefix."""
|
|
55
|
+
|
|
56
|
+
NOTE = (
|
|
57
|
+
"<!-- Generated by chipgraph from the project profile; do not edit this block by hand. "
|
|
58
|
+
"Regenerate it with `chipgraph agents-md --write`. -->"
|
|
59
|
+
)
|
|
60
|
+
"""The one-line note that opens the managed block."""
|
|
61
|
+
|
|
62
|
+
_TEMPLATE_DIR = Path(__file__).resolve().parent
|
|
63
|
+
|
|
64
|
+
_BLANK_RUNS = re.compile(r"\n{3,}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def code(value: object, cell: bool = False) -> str:
|
|
68
|
+
"""`value` as a Markdown code span; with `cell`, `|` is escaped for a table cell."""
|
|
69
|
+
s = " ".join(str(value).split())
|
|
70
|
+
if not s:
|
|
71
|
+
return ""
|
|
72
|
+
fence = "``" if "`" in s else "`"
|
|
73
|
+
pad = " " if fence == "``" else ""
|
|
74
|
+
if cell:
|
|
75
|
+
s = s.replace("|", "\\|")
|
|
76
|
+
return f"{fence}{pad}{s}{pad}{fence}"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def text(value: object) -> str:
|
|
80
|
+
"""`value` as one line of Markdown text, safe in a table cell.
|
|
81
|
+
|
|
82
|
+
Only `<` (else read as an HTML tag, e.g. ``m_qnsc_<function>``) and `|` (a cell
|
|
83
|
+
separator) are escaped; other Markdown in the value is kept as written.
|
|
84
|
+
"""
|
|
85
|
+
s = " ".join(str(value).split())
|
|
86
|
+
for ch in ("<", "|"):
|
|
87
|
+
s = s.replace(ch, "\\" + ch)
|
|
88
|
+
return s
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _normalize(rendered: str) -> str:
|
|
92
|
+
lines = [line.rstrip() for line in rendered.replace("\r\n", "\n").split("\n")]
|
|
93
|
+
out = _BLANK_RUNS.sub("\n\n", "\n".join(lines)).strip("\n")
|
|
94
|
+
return out + "\n" if out else ""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _template_set(resolved: ResolvedProfile, root: Path) -> TemplateSet:
|
|
98
|
+
overrides = {
|
|
99
|
+
name: str(path if (path := Path(value)).is_absolute() else root / path)
|
|
100
|
+
for name, value in sorted(resolved.profile.templates.override.items())
|
|
101
|
+
if name == TEMPLATE_NAME or name.startswith(BLOCK_PREFIX)
|
|
102
|
+
}
|
|
103
|
+
layers: list[TemplateLayer] = []
|
|
104
|
+
if overrides:
|
|
105
|
+
layers.append(TemplateLayer(name="project", override=overrides))
|
|
106
|
+
layers.append(TemplateLayer(name="pack", directory=_TEMPLATE_DIR))
|
|
107
|
+
return TemplateSet(layers)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def render_agents_md(resolved_profile: ResolvedProfile, root: Path) -> str:
|
|
111
|
+
"""The managed block's content for the project at `root`, without the markers.
|
|
112
|
+
|
|
113
|
+
It starts with the one-line generated note, then one section per kind of profile data
|
|
114
|
+
present. The same profile always gives the same bytes. Raises `AgentsMdError` if the
|
|
115
|
+
naming rules cannot be read or a template override is broken.
|
|
116
|
+
"""
|
|
117
|
+
context = build_context(resolved_profile, root)
|
|
118
|
+
context["code"] = code
|
|
119
|
+
context["text"] = text
|
|
120
|
+
try:
|
|
121
|
+
body = _template_set(resolved_profile, root).render(TEMPLATE_NAME, context)
|
|
122
|
+
except TemplateSetError as exc:
|
|
123
|
+
raise AgentsMdError(f"cannot render the AGENTS.md block: {exc}") from exc
|
|
124
|
+
block = _normalize(body)
|
|
125
|
+
if BEGIN_MARKER in block or END_MARKER in block:
|
|
126
|
+
raise AgentsMdError("the rendered AGENTS.md block must not contain the chipgraph markers")
|
|
127
|
+
return f"{NOTE}\n\n{block}"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def agents_md_content(existing: str | None, resolved_profile: ResolvedProfile, root: Path) -> str:
|
|
131
|
+
"""The full file content: `existing` with its managed block (re)generated."""
|
|
132
|
+
return update_agents_md(existing, render_agents_md(resolved_profile, root))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__all__ = [
|
|
136
|
+
"BEGIN_MARKER",
|
|
137
|
+
"BLOCK_PREFIX",
|
|
138
|
+
"END_MARKER",
|
|
139
|
+
"NOTE",
|
|
140
|
+
"TEMPLATE_NAME",
|
|
141
|
+
"AgentsMdError",
|
|
142
|
+
"agents_md_content",
|
|
143
|
+
"code",
|
|
144
|
+
"render_agents_md",
|
|
145
|
+
"split_managed",
|
|
146
|
+
"text",
|
|
147
|
+
"update_agents_md",
|
|
148
|
+
]
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Build the template context for the managed `AGENTS.md` block from a resolved profile.
|
|
2
|
+
|
|
3
|
+
Everything here is plain data (dicts, lists, strings, booleans), sorted where the profile
|
|
4
|
+
has no meaningful order, so the rendered block is deterministic. No path in the context
|
|
5
|
+
is made absolute: values are shown as the profile spells them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
from pydantic import ValidationError
|
|
16
|
+
|
|
17
|
+
from chipgraph.checks._naming_rules import NamingRules
|
|
18
|
+
from chipgraph.core.config import ConfigError, ResolvedProfile, resolve_data_ref
|
|
19
|
+
from chipgraph.core.config.models import AdapterCfg, PathRule
|
|
20
|
+
from chipgraph.packs.spec_core.gen.agents_md._markers import AgentsMdError
|
|
21
|
+
|
|
22
|
+
_WS = re.compile(r"\s+")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _one_line(value: object) -> str:
|
|
26
|
+
return _WS.sub(" ", str(value)).strip()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _as_list(value: object) -> list[str]:
|
|
30
|
+
if value is None:
|
|
31
|
+
return []
|
|
32
|
+
if isinstance(value, str):
|
|
33
|
+
return [value]
|
|
34
|
+
if isinstance(value, list | tuple):
|
|
35
|
+
return [str(v) for v in value]
|
|
36
|
+
return [str(value)]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _command(value: object) -> str | None:
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
return _one_line(value)
|
|
42
|
+
if isinstance(value, list | tuple) and value:
|
|
43
|
+
return " ".join(str(v) for v in value)
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _check_row(check_id: str, cfg: AdapterCfg) -> dict[str, Any]:
|
|
48
|
+
extras = cfg.model_dump()
|
|
49
|
+
severity = extras.get("severity")
|
|
50
|
+
return {
|
|
51
|
+
"id": check_id,
|
|
52
|
+
"use": cfg.use,
|
|
53
|
+
"severity": str(severity) if severity is not None else None,
|
|
54
|
+
"command": _command(extras.get("cmd")) if cfg.use == "cmd" else None,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _path_row(glob: str, rule: PathRule, block: str | None) -> dict[str, Any]:
|
|
59
|
+
return {
|
|
60
|
+
"glob": glob,
|
|
61
|
+
"block": block,
|
|
62
|
+
"checks": [f"{name} {rule.checks[name]}" for name in sorted(rule.checks)],
|
|
63
|
+
"write": rule.write,
|
|
64
|
+
"reason": _one_line(rule.reason) if rule.reason else None,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _load_naming(ref: str, root: Path) -> dict[str, Any]:
|
|
69
|
+
try:
|
|
70
|
+
path = resolve_data_ref(ref, root)
|
|
71
|
+
except ConfigError as exc:
|
|
72
|
+
raise AgentsMdError(f"naming rules {ref!r}: {exc}") from exc
|
|
73
|
+
try:
|
|
74
|
+
data = yaml.safe_load(path.read_bytes())
|
|
75
|
+
except (OSError, yaml.YAMLError) as exc:
|
|
76
|
+
raise AgentsMdError(f"naming rules {ref!r}: cannot read: {exc}") from exc
|
|
77
|
+
try:
|
|
78
|
+
rules = NamingRules.model_validate(data)
|
|
79
|
+
except ValidationError as exc:
|
|
80
|
+
first = exc.errors()[0]
|
|
81
|
+
loc = ".".join(str(p) for p in first["loc"])
|
|
82
|
+
raise AgentsMdError(f"naming rules {ref!r}: invalid: {loc}: {first['msg']}") from exc
|
|
83
|
+
vocabulary = None
|
|
84
|
+
if rules.vocabulary is not None:
|
|
85
|
+
vocabulary = {
|
|
86
|
+
"rule": rules.vocabulary.rule,
|
|
87
|
+
"pairs": [[k, rules.vocabulary.replace[k]] for k in sorted(rules.vocabulary.replace)],
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
"ref": ref,
|
|
91
|
+
"document": rules.document,
|
|
92
|
+
"version": rules.version,
|
|
93
|
+
"ignore_comment": rules.ignore_comment,
|
|
94
|
+
# The rule file's own order (module, port, ...) is kept: it follows the source
|
|
95
|
+
# document, and it is as deterministic as the file itself.
|
|
96
|
+
"identifiers": [
|
|
97
|
+
{
|
|
98
|
+
"kind": kind,
|
|
99
|
+
"pattern": rule.pattern,
|
|
100
|
+
"rule": rule.rule,
|
|
101
|
+
"message": _one_line(rule.message),
|
|
102
|
+
}
|
|
103
|
+
for kind, rule in rules.identifiers.items()
|
|
104
|
+
],
|
|
105
|
+
"lexical": [{"rule": lex.rule, "message": _one_line(lex.message)} for lex in rules.lexical],
|
|
106
|
+
"vocabulary": vocabulary,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _naming_refs(resolved: ResolvedProfile) -> list[str]:
|
|
111
|
+
profile = resolved.profile
|
|
112
|
+
refs: list[str] = []
|
|
113
|
+
if profile.naming.rules:
|
|
114
|
+
refs.append(profile.naming.rules)
|
|
115
|
+
for check_id in sorted(profile.adapters):
|
|
116
|
+
cfg = profile.adapters[check_id]
|
|
117
|
+
rules = cfg.model_dump().get("rules")
|
|
118
|
+
if cfg.use == "naming" and isinstance(rules, str) and rules not in refs:
|
|
119
|
+
refs.append(rules)
|
|
120
|
+
return refs
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _generated(resolved: ResolvedProfile) -> list[str]:
|
|
124
|
+
profile = resolved.profile
|
|
125
|
+
globs: set[str] = set(_as_list(profile.layout.get("generated")))
|
|
126
|
+
for cfg in profile.adapters.values():
|
|
127
|
+
extras = cfg.model_dump()
|
|
128
|
+
if cfg.use == "generated":
|
|
129
|
+
globs.update(_as_list(extras.get("files")))
|
|
130
|
+
globs.update(_as_list(extras.get("generated")))
|
|
131
|
+
return sorted(globs)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _requirements(resolved: ResolvedProfile) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
135
|
+
profile = resolved.profile
|
|
136
|
+
req = profile.spec.requirements
|
|
137
|
+
per_block: list[dict[str, Any]] = []
|
|
138
|
+
for name in sorted(profile.blocks):
|
|
139
|
+
if profile.blocks[name].spec.requirements is None:
|
|
140
|
+
continue
|
|
141
|
+
eff = resolved.for_block(name).spec.requirements
|
|
142
|
+
per_block.append(
|
|
143
|
+
{"block": name, "id_pattern": eff.id_pattern, "infer": eff.infer == "verification"}
|
|
144
|
+
)
|
|
145
|
+
uses_block = "{block}" in req.id_pattern or "{BLOCK}" in req.id_pattern
|
|
146
|
+
overridden = {row["block"] for row in per_block}
|
|
147
|
+
example = None
|
|
148
|
+
if uses_block:
|
|
149
|
+
candidates = [b for b in sorted(profile.blocks) if b not in overridden]
|
|
150
|
+
if candidates:
|
|
151
|
+
example = {"block": candidates[0], "pattern": req.id_regex(candidates[0]).pattern}
|
|
152
|
+
trace = [cid for cid in sorted(profile.adapters) if profile.adapters[cid].use == "trace"]
|
|
153
|
+
return (
|
|
154
|
+
{
|
|
155
|
+
"id_pattern": req.id_pattern,
|
|
156
|
+
"uses_block": uses_block,
|
|
157
|
+
"example": example,
|
|
158
|
+
"infer": req.infer == "verification",
|
|
159
|
+
"infer_heading": req.infer_heading,
|
|
160
|
+
"trace_check": trace[0] if trace else None,
|
|
161
|
+
},
|
|
162
|
+
per_block,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def build_context(resolved: ResolvedProfile, root: Path) -> dict[str, Any]:
|
|
167
|
+
"""The data the `agents_md.md.j2` template renders, from `resolved` and `root`.
|
|
168
|
+
|
|
169
|
+
`root` is only used to resolve the naming rules reference (``org:``, ``preset:``,
|
|
170
|
+
``path:`` or relative), exactly as the `naming` check does.
|
|
171
|
+
"""
|
|
172
|
+
profile = resolved.profile
|
|
173
|
+
|
|
174
|
+
layout = [
|
|
175
|
+
{"kind": kind, "templates": _as_list(profile.layout[kind])}
|
|
176
|
+
for kind in sorted(profile.layout)
|
|
177
|
+
]
|
|
178
|
+
block_layout = [
|
|
179
|
+
{"block": name, "kind": kind, "templates": _as_list(override.layout[kind])}
|
|
180
|
+
for name, override in sorted(profile.blocks.items())
|
|
181
|
+
for kind in sorted(override.layout)
|
|
182
|
+
]
|
|
183
|
+
all_templates = [t for row in (*layout, *block_layout) for t in row["templates"]]
|
|
184
|
+
|
|
185
|
+
block_checks = [
|
|
186
|
+
{"block": name, **_check_row(check_id, override.adapters[check_id])}
|
|
187
|
+
for name, override in sorted(profile.blocks.items())
|
|
188
|
+
for check_id in sorted(override.adapters)
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
paths = [_path_row(glob, profile.paths[glob], None) for glob in sorted(profile.paths)]
|
|
192
|
+
paths += [
|
|
193
|
+
_path_row(glob, override.paths[glob], name)
|
|
194
|
+
for name, override in sorted(profile.blocks.items())
|
|
195
|
+
for glob in sorted(override.paths)
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
checker = profile.naming.checker
|
|
199
|
+
requirements, block_requirements = _requirements(resolved)
|
|
200
|
+
chip = profile.spec.chip
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
"project": {
|
|
204
|
+
"name": profile.project,
|
|
205
|
+
"extends": list(profile.extends),
|
|
206
|
+
"packs": list(profile.packs),
|
|
207
|
+
"target_kind": profile.target.kind,
|
|
208
|
+
"pdk": profile.target.pdk,
|
|
209
|
+
"chip_spec": {"path": chip.path, "format": chip.format} if chip else None,
|
|
210
|
+
"ip_dir": profile.spec.ip_dir,
|
|
211
|
+
},
|
|
212
|
+
"blocks": [
|
|
213
|
+
{"name": name, "instances": list(profile.blocks[name].instances)}
|
|
214
|
+
for name in sorted(profile.blocks)
|
|
215
|
+
],
|
|
216
|
+
"layout": layout,
|
|
217
|
+
"block_layout": block_layout,
|
|
218
|
+
"placeholders": [p for p in ("{block}", "{BLOCK}") if any(p in t for t in all_templates)],
|
|
219
|
+
"naming": [_load_naming(ref, root) for ref in _naming_refs(resolved)],
|
|
220
|
+
"naming_checker": (
|
|
221
|
+
{"use": checker.use, "command": _command(checker.model_dump().get("cmd"))}
|
|
222
|
+
if checker is not None
|
|
223
|
+
else None
|
|
224
|
+
),
|
|
225
|
+
"checks": [_check_row(cid, profile.adapters[cid]) for cid in sorted(profile.adapters)],
|
|
226
|
+
"block_checks": block_checks,
|
|
227
|
+
"requirements": requirements,
|
|
228
|
+
"block_requirements": block_requirements,
|
|
229
|
+
"paths": paths,
|
|
230
|
+
"nda": (
|
|
231
|
+
{"paths": sorted(set(profile.data.nda_paths)), "model": profile.data.nda_model}
|
|
232
|
+
if profile.data.nda_paths
|
|
233
|
+
else None
|
|
234
|
+
),
|
|
235
|
+
"generated": _generated(resolved),
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
__all__ = ["build_context"]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""The managed block of an `AGENTS.md`: find the markers, replace only what is between.
|
|
2
|
+
|
|
3
|
+
DESIGN.md 4.7 / DECISIONS D31: chipgraph owns only the text between
|
|
4
|
+
``<!-- chipgraph:begin -->`` and ``<!-- chipgraph:end -->``; everything else in the file
|
|
5
|
+
belongs to the team and is kept byte for byte.
|
|
6
|
+
|
|
7
|
+
A marker is recognised only as a whole line (surrounding spaces, tabs and a trailing
|
|
8
|
+
``\\r`` allowed), so a team's own prose that mentions the marker inline is not mistaken
|
|
9
|
+
for one. Malformed markers are an error, never a guess.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
BEGIN_MARKER = "<!-- chipgraph:begin -->"
|
|
15
|
+
END_MARKER = "<!-- chipgraph:end -->"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AgentsMdError(Exception):
|
|
19
|
+
"""The file's markers are malformed, or the managed block could not be rendered."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _marker_lines(text: str, marker: str) -> list[tuple[int, int, int]]:
|
|
23
|
+
"""Every whole-line occurrence of `marker`: (line number, start offset, end offset).
|
|
24
|
+
|
|
25
|
+
The offsets span the marker line without its line ending.
|
|
26
|
+
"""
|
|
27
|
+
found: list[tuple[int, int, int]] = []
|
|
28
|
+
offset = 0
|
|
29
|
+
for number, line in enumerate(text.splitlines(keepends=True), start=1):
|
|
30
|
+
body = line.rstrip("\r\n")
|
|
31
|
+
if body.strip(" \t") == marker:
|
|
32
|
+
found.append((number, offset, offset + len(body)))
|
|
33
|
+
offset += len(line)
|
|
34
|
+
return found
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def split_managed(existing: str) -> tuple[str, str, str] | None:
|
|
38
|
+
"""Split `existing` into (text up to and including the begin marker line's marker,
|
|
39
|
+
the managed text between the markers, text from the end marker on).
|
|
40
|
+
|
|
41
|
+
Returns `None` if the file has no markers at all. Raises `AgentsMdError` for a begin
|
|
42
|
+
marker without an end, an end without a begin, more than one of either, or an end
|
|
43
|
+
marker before the begin marker.
|
|
44
|
+
"""
|
|
45
|
+
begins = _marker_lines(existing, BEGIN_MARKER)
|
|
46
|
+
ends = _marker_lines(existing, END_MARKER)
|
|
47
|
+
if not begins and not ends:
|
|
48
|
+
return None
|
|
49
|
+
if len(begins) > 1:
|
|
50
|
+
lines = ", ".join(str(n) for n, _, _ in begins)
|
|
51
|
+
raise AgentsMdError(
|
|
52
|
+
f"{BEGIN_MARKER} appears {len(begins)} times (lines {lines}); keep exactly one "
|
|
53
|
+
"managed block"
|
|
54
|
+
)
|
|
55
|
+
if len(ends) > 1:
|
|
56
|
+
lines = ", ".join(str(n) for n, _, _ in ends)
|
|
57
|
+
raise AgentsMdError(
|
|
58
|
+
f"{END_MARKER} appears {len(ends)} times (lines {lines}); keep exactly one "
|
|
59
|
+
"managed block"
|
|
60
|
+
)
|
|
61
|
+
if not ends:
|
|
62
|
+
raise AgentsMdError(
|
|
63
|
+
f"{BEGIN_MARKER} on line {begins[0][0]} has no matching {END_MARKER}; add the "
|
|
64
|
+
"end marker or remove the begin marker"
|
|
65
|
+
)
|
|
66
|
+
if not begins:
|
|
67
|
+
raise AgentsMdError(
|
|
68
|
+
f"{END_MARKER} on line {ends[0][0]} has no {BEGIN_MARKER} before it; add the "
|
|
69
|
+
"begin marker or remove the end marker"
|
|
70
|
+
)
|
|
71
|
+
begin_line, _, begin_end = begins[0]
|
|
72
|
+
end_line, end_start, _ = ends[0]
|
|
73
|
+
if end_line < begin_line:
|
|
74
|
+
raise AgentsMdError(
|
|
75
|
+
f"{END_MARKER} (line {end_line}) comes before {BEGIN_MARKER} (line "
|
|
76
|
+
f"{begin_line}); the begin marker must come first"
|
|
77
|
+
)
|
|
78
|
+
# The begin marker's own line ending stays with the managed text's replacement.
|
|
79
|
+
return existing[:begin_end], existing[begin_end:end_start], existing[end_start:]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _check_block(block: str) -> None:
|
|
83
|
+
if _marker_lines(block, BEGIN_MARKER) or _marker_lines(block, END_MARKER):
|
|
84
|
+
raise AgentsMdError("the managed block must not contain the chipgraph markers itself")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def wrap_block(block: str) -> str:
|
|
88
|
+
"""`block` between the two markers, newline-terminated."""
|
|
89
|
+
_check_block(block)
|
|
90
|
+
body = block if block.endswith("\n") or not block else block + "\n"
|
|
91
|
+
return f"{BEGIN_MARKER}\n{body}{END_MARKER}\n"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def update_agents_md(existing: str | None, block: str) -> str:
|
|
95
|
+
"""The file content after putting `block` in the managed part of `existing`.
|
|
96
|
+
|
|
97
|
+
- `existing` is `None` (no file) or empty: the markers with `block` between them.
|
|
98
|
+
- `existing` has no markers: the team's content unchanged, one blank line, then the
|
|
99
|
+
marked block appended.
|
|
100
|
+
- `existing` has one well-formed pair: only the text between the markers is replaced;
|
|
101
|
+
every byte outside them (and the marker lines themselves) is kept.
|
|
102
|
+
- Malformed markers raise `AgentsMdError`.
|
|
103
|
+
|
|
104
|
+
`block` is the managed content without the markers (as `render_agents_md` returns).
|
|
105
|
+
Applying the same `block` twice gives the same result as applying it once.
|
|
106
|
+
"""
|
|
107
|
+
_check_block(block)
|
|
108
|
+
if existing is None or existing == "":
|
|
109
|
+
return wrap_block(block)
|
|
110
|
+
parts = split_managed(existing)
|
|
111
|
+
if parts is None:
|
|
112
|
+
if existing.endswith("\n\n"):
|
|
113
|
+
sep = ""
|
|
114
|
+
elif existing.endswith("\n"):
|
|
115
|
+
sep = "\n"
|
|
116
|
+
else:
|
|
117
|
+
sep = "\n\n"
|
|
118
|
+
return existing + sep + wrap_block(block)
|
|
119
|
+
head, _, tail = parts
|
|
120
|
+
body = block if block.endswith("\n") or not block else block + "\n"
|
|
121
|
+
return f"{head}\n{body}{tail}"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
__all__ = [
|
|
125
|
+
"BEGIN_MARKER",
|
|
126
|
+
"END_MARKER",
|
|
127
|
+
"AgentsMdError",
|
|
128
|
+
"split_managed",
|
|
129
|
+
"update_agents_md",
|
|
130
|
+
"wrap_block",
|
|
131
|
+
]
|