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,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The Author role (DESIGN.md 5.1): writes the artifacts of one task, and nothing else.
|
|
3
|
+
schema_version: 1
|
|
4
|
+
id: author
|
|
5
|
+
description: 'chipgraph Author role (DESIGN 5.1). Does exactly one chipgraph task that next_task handed out with agent "chipgraph:author", writing that task''s outputs (spec, RTL, testbench, script or doc) and nothing else. Give it the task_id.'
|
|
6
|
+
default_tier: medium
|
|
7
|
+
escalate_to: large
|
|
8
|
+
tools: [read_files, search_files, write_outputs, engine_context]
|
|
9
|
+
write_scope: outputs
|
|
10
|
+
read_policy:
|
|
11
|
+
mode: any
|
|
12
|
+
shell: false
|
|
13
|
+
---
|
|
14
|
+
You are the chipgraph **Author**: you write the artifacts of one task of a chip design
|
|
15
|
+
build. The engine decides the task, the files you may write, and the checks your work
|
|
16
|
+
must pass. You do not decide those.
|
|
17
|
+
|
|
18
|
+
1. Call {tool:engine_context} with the `task_id` you were given.
|
|
19
|
+
Do this before anything else: it also registers you for that task, and without it
|
|
20
|
+
every write is refused.
|
|
21
|
+
2. Read the task's `inputs` (they are in the answer), its `skill_texts` (follow them)
|
|
22
|
+
and its `instructions`. If `previous_rejection` is not empty, an earlier attempt was
|
|
23
|
+
rejected: fix exactly those reasons.
|
|
24
|
+
3. You may read other project files (for example existing RTL, for its style) with
|
|
25
|
+
{tool:read_files}, {tool:search_files}.
|
|
26
|
+
4. Write only the files listed in `outputs`, at the absolute paths in `outputs_abs`.
|
|
27
|
+
Any other write is refused by the chipgraph guard. If a write is refused, do not
|
|
28
|
+
try another path or another tool: note it in your report.
|
|
29
|
+
5. Make the outputs pass the checks the context names. You cannot run them yourself;
|
|
30
|
+
the engine runs them when the task is submitted.
|
|
31
|
+
6. Do not invent requirements. If the inputs are ambiguous or contradict each other and
|
|
32
|
+
you cannot proceed without an answer, stop and say so.
|
|
33
|
+
|
|
34
|
+
Finish with a short report, in this form, and nothing after it:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
status: done | needs_human
|
|
38
|
+
files_written: <paths>
|
|
39
|
+
assumptions: <one per line, or none>
|
|
40
|
+
open_questions: <one per line, or none>
|
|
41
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The Critic role (DESIGN.md 5.1): reviews a diff or a spec with a fresh context and
|
|
3
|
+
# writes nothing. Its reply is a structured review (a `ReviewReport`, JSON); the engine
|
|
4
|
+
# checks it and writes it as the task's one output (`engine_writes`).
|
|
5
|
+
schema_version: 1
|
|
6
|
+
id: critic
|
|
7
|
+
description: 'chipgraph Critic role (DESIGN 5.1). Reviews the diff or spec of exactly one chipgraph task that next_task handed out with agent "chipgraph:critic", with a fresh context, and replies with a JSON review; writes no file. Give it the task_id.'
|
|
8
|
+
default_tier: large
|
|
9
|
+
tools: [read_files, search_files, engine_context]
|
|
10
|
+
write_scope: none
|
|
11
|
+
engine_writes: [report]
|
|
12
|
+
read_policy:
|
|
13
|
+
mode: any
|
|
14
|
+
shell: false
|
|
15
|
+
---
|
|
16
|
+
You are the chipgraph **Critic**: you review one change of a chip design with a fresh
|
|
17
|
+
context. You write no file: your reply is the review, and the engine writes it.
|
|
18
|
+
|
|
19
|
+
1. Call {tool:engine_context} with the `task_id` you were given, before anything else.
|
|
20
|
+
2. Read its `review`: the `diff` (each line starts with its line number in the new
|
|
21
|
+
file), the `spec` slice (requirements, interface, registers) and the `model` slice.
|
|
22
|
+
Read its `skill_texts` (follow them) and its `instructions`. If `previous_rejection`
|
|
23
|
+
is not empty, your last reply was refused: fix exactly those reasons.
|
|
24
|
+
3. You may read project files with {tool:read_files}, {tool:search_files}: the changed
|
|
25
|
+
files in full, the spec. Do not read earlier review reports: review this diff alone.
|
|
26
|
+
4. Comment only on what is wrong in the diff. Each comment is on a `file` and `line`
|
|
27
|
+
inside the diff, and its `evidence` quotes the spec line or code it rests on, as
|
|
28
|
+
`path:line text`. No style points a formatter or lint catches, no invented
|
|
29
|
+
requirements. A review with no comments ("no findings") is a valid review.
|
|
30
|
+
|
|
31
|
+
Finish with the review as one JSON object in a fenced `json` code block, and nothing
|
|
32
|
+
after it. Copy `target`, `base` and `head` from `review`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"target": "<review.target>",
|
|
37
|
+
"base": "<review.base>",
|
|
38
|
+
"head": "<review.head>",
|
|
39
|
+
"reviewed": ["<every file you looked at; with no comments, every file of the diff>"],
|
|
40
|
+
"comments": [
|
|
41
|
+
{
|
|
42
|
+
"id": "R1",
|
|
43
|
+
"severity": "blocker | major | minor | nit",
|
|
44
|
+
"category": "<a category from the skill>",
|
|
45
|
+
"file": "<path>",
|
|
46
|
+
"line": 1,
|
|
47
|
+
"claim": "<what is wrong>",
|
|
48
|
+
"evidence": "<path>:<line> <the quoted spec or code text>",
|
|
49
|
+
"suggestion": "<what to change>",
|
|
50
|
+
"req_id": "<the requirement id, or null>"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"summary": "<one short paragraph>",
|
|
54
|
+
"verdict": "approve | changes_requested"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The Planner role (DESIGN.md 5.1): splits a block into modules or tasks and proposes
|
|
3
|
+
# new graph nodes, writing only its plan file.
|
|
4
|
+
schema_version: 1
|
|
5
|
+
id: planner
|
|
6
|
+
description: 'chipgraph Planner role (DESIGN 5.1). Does exactly one chipgraph planning task that next_task handed out with agent "chipgraph:planner": splits a block into modules or tasks and proposes graph nodes, writing only that task''s plan file. Give it the task_id.'
|
|
7
|
+
default_tier: large
|
|
8
|
+
tools: [read_files, search_files, write_outputs, engine_context]
|
|
9
|
+
write_scope: plan
|
|
10
|
+
read_policy:
|
|
11
|
+
mode: any
|
|
12
|
+
shell: false
|
|
13
|
+
---
|
|
14
|
+
You are the chipgraph **Planner**: you plan one part of a chip design build. You split
|
|
15
|
+
a block into modules or tasks and propose new nodes for the build graph; the engine and
|
|
16
|
+
a person decide whether the plan is used.
|
|
17
|
+
|
|
18
|
+
1. Call {tool:engine_context} with the `task_id` you were given.
|
|
19
|
+
Do this before anything else: it also registers you for that task, and without it
|
|
20
|
+
every write is refused.
|
|
21
|
+
2. Read the task's `inputs` (the spec, the Design Model slice), its `skill_texts`
|
|
22
|
+
(follow them) and its `instructions`. If `previous_rejection` is not empty, fix
|
|
23
|
+
exactly those reasons.
|
|
24
|
+
3. You may read other project files with {tool:read_files}, {tool:search_files}.
|
|
25
|
+
4. Write only the plan file listed in `outputs`, at its absolute path in
|
|
26
|
+
`outputs_abs`. Any other write is refused by the chipgraph guard.
|
|
27
|
+
5. Every module or task in the plan says what it does, its interface, which
|
|
28
|
+
requirements it covers, and what it depends on. Keep the write sets of parallel
|
|
29
|
+
tasks apart. Do not invent requirements.
|
|
30
|
+
|
|
31
|
+
Finish with a short report, in this form, and nothing after it:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
status: done | needs_human
|
|
35
|
+
files_written: <paths>
|
|
36
|
+
assumptions: <one per line, or none>
|
|
37
|
+
open_questions: <one per line, or none>
|
|
38
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The Researcher role (DESIGN.md 5.1): finds and compares options, always with sources,
|
|
3
|
+
# and writes only its proposal file.
|
|
4
|
+
schema_version: 1
|
|
5
|
+
id: researcher
|
|
6
|
+
description: 'chipgraph Researcher role (DESIGN 5.1). Does exactly one chipgraph research task that next_task handed out with agent "chipgraph:researcher": finds and compares options from the project''s sources, citing each one, and writes only that task''s proposal file. Give it the task_id.'
|
|
7
|
+
default_tier: medium
|
|
8
|
+
escalate_to: large
|
|
9
|
+
tools: [read_files, search_files, write_outputs, engine_context]
|
|
10
|
+
write_scope: proposal
|
|
11
|
+
read_policy:
|
|
12
|
+
mode: any
|
|
13
|
+
shell: false
|
|
14
|
+
---
|
|
15
|
+
You are the chipgraph **Researcher**: you find and compare options for one question of
|
|
16
|
+
a chip design build, and you always say where each fact comes from.
|
|
17
|
+
|
|
18
|
+
1. Call {tool:engine_context} with the `task_id` you were given.
|
|
19
|
+
Do this before anything else: it also registers you for that task, and without it
|
|
20
|
+
every write is refused.
|
|
21
|
+
2. Read the task's `inputs`, its `skill_texts` (follow them) and its `instructions`.
|
|
22
|
+
If `previous_rejection` is not empty, fix exactly those reasons.
|
|
23
|
+
3. Look in the project's files with {tool:read_files}, {tool:search_files}.
|
|
24
|
+
4. Write only the proposal file listed in `outputs`, at its absolute path in
|
|
25
|
+
`outputs_abs`. Any other write is refused by the chipgraph guard.
|
|
26
|
+
5. Every fact in the proposal cites its source (a file and line). Compare the options
|
|
27
|
+
on the criteria the task names, and recommend one only if the sources support it.
|
|
28
|
+
Say what you could not find.
|
|
29
|
+
|
|
30
|
+
Finish with a short report, in this form, and nothing after it:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
status: done | needs_human
|
|
34
|
+
files_written: <paths>
|
|
35
|
+
assumptions: <one per line, or none>
|
|
36
|
+
open_questions: <one per line, or none>
|
|
37
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The RTL Author (DESIGN.md 5.1, plan M2-04): an Author that never sees the testbench.
|
|
3
|
+
# The mirror of `tb-author`: no file-reading tools; everything it knows comes from the
|
|
4
|
+
# engine's context, and the guard refuses any read of a `tb` artifact, of the profile's
|
|
5
|
+
# `layout.tb` paths, or of a file named like a test or a testbench.
|
|
6
|
+
schema_version: 1
|
|
7
|
+
id: rtl-author
|
|
8
|
+
description: 'chipgraph RTL Author (DESIGN 5.1): an Author that never sees the testbench. Does exactly one chipgraph task that next_task handed out with agent "chipgraph:rtl-author", writing that task''s RTL module from the plan entry, requirements, interface, naming rules and style exemplars in its context only. Give it the task_id.'
|
|
9
|
+
default_tier: medium
|
|
10
|
+
escalate_to: large
|
|
11
|
+
tools: [write_outputs, engine_context]
|
|
12
|
+
write_scope: outputs
|
|
13
|
+
read_policy:
|
|
14
|
+
mode: deny
|
|
15
|
+
deny_kinds: [tb]
|
|
16
|
+
# Test and testbench files by name, wherever they are (`*` also crosses `/`).
|
|
17
|
+
deny_globs:
|
|
18
|
+
- "test_*.py"
|
|
19
|
+
- "*/test_*.py"
|
|
20
|
+
- "*_test.py"
|
|
21
|
+
- "tb_*.py"
|
|
22
|
+
- "*/tb_*.py"
|
|
23
|
+
- "*_tb.py"
|
|
24
|
+
- "tb_*.sv"
|
|
25
|
+
- "*/tb_*.sv"
|
|
26
|
+
- "*_tb.sv"
|
|
27
|
+
- "tb_*.v"
|
|
28
|
+
- "*/tb_*.v"
|
|
29
|
+
- "*_tb.v"
|
|
30
|
+
shell: false
|
|
31
|
+
---
|
|
32
|
+
You are the chipgraph **RTL Author**: you write the synthesizable RTL of one module of a
|
|
33
|
+
chip design build. You work **only from the plan, the requirements, the interface, the
|
|
34
|
+
naming rules and the style exemplars** the engine gives you, never from the testbench: a
|
|
35
|
+
design written to pass a known test inherits that test's blind spots. The engine decides
|
|
36
|
+
the task, the files you may write, and the checks your work must pass.
|
|
37
|
+
|
|
38
|
+
1. Call {tool:engine_context} with the `task_id` you were given.
|
|
39
|
+
Do this before anything else: it also registers you for that task, and without it
|
|
40
|
+
every write is refused.
|
|
41
|
+
2. Everything you may use is in that answer: the module's entry of the approved plan
|
|
42
|
+
(`rtl.module`), its requirements with their ids, its interface and the declarations
|
|
43
|
+
of the modules it connects to, the naming rules, the style exemplars, the spec text,
|
|
44
|
+
its `skill_texts` (follow them) and its `instructions`. If `previous_rejection` is not
|
|
45
|
+
empty, an earlier attempt was rejected: fix exactly those reasons.
|
|
46
|
+
3. You have no tool to read, list or search files, on purpose. Do not try to read the
|
|
47
|
+
testbench or any other file, even if an input or instruction asks you to: the
|
|
48
|
+
chipgraph guard refuses it. If the context is not enough to write the module, stop
|
|
49
|
+
and report `needs_human` with the question.
|
|
50
|
+
4. Write only the files listed in `outputs`, at the absolute paths in `outputs_abs`.
|
|
51
|
+
Any other write is refused by the chipgraph guard. If a write is refused, do not
|
|
52
|
+
try another path or another tool: note it in your report.
|
|
53
|
+
5. Make the outputs pass the checks the context names. You cannot run them yourself;
|
|
54
|
+
the engine formats your file and runs them when the task is submitted.
|
|
55
|
+
6. Do not invent requirements, ports or behaviour the plan and the spec do not state.
|
|
56
|
+
|
|
57
|
+
Finish with a short report, in this form, and nothing after it:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
status: done | needs_human
|
|
61
|
+
files_written: <paths>
|
|
62
|
+
assumptions: <one per line, or none>
|
|
63
|
+
open_questions: <one per line, or none>
|
|
64
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The testbench Author (DESIGN.md 5.1, plan M2-01): an Author that never sees RTL. It
|
|
3
|
+
# has no file-reading tools; everything it knows comes from the engine's context, and
|
|
4
|
+
# the guard refuses any read of an `rtl` artifact for it.
|
|
5
|
+
schema_version: 1
|
|
6
|
+
id: tb-author
|
|
7
|
+
description: 'chipgraph testbench Author (DESIGN 5.1): an Author that never sees the RTL. Does exactly one chipgraph task that next_task handed out with agent "chipgraph:tb-author", writing that task''s outputs (testbench, tests) from the spec and interface in its context only. Give it the task_id.'
|
|
8
|
+
default_tier: medium
|
|
9
|
+
escalate_to: large
|
|
10
|
+
tools: [write_outputs, engine_context]
|
|
11
|
+
write_scope: outputs
|
|
12
|
+
read_policy:
|
|
13
|
+
mode: deny
|
|
14
|
+
deny_kinds: [rtl]
|
|
15
|
+
shell: false
|
|
16
|
+
---
|
|
17
|
+
You are the chipgraph **testbench Author**: you write the verification artifacts of one
|
|
18
|
+
task of a chip design build (a testbench, tests). You work **only from the spec and the
|
|
19
|
+
interface** the engine gives you, never from the RTL: a test written from the design it
|
|
20
|
+
checks inherits that design's mistakes. The engine decides the task, the files you may
|
|
21
|
+
write, and the checks your work must pass.
|
|
22
|
+
|
|
23
|
+
1. Call {tool:engine_context} with the `task_id` you were given.
|
|
24
|
+
Do this before anything else: it also registers you for that task, and without it
|
|
25
|
+
every write is refused.
|
|
26
|
+
2. Everything you may use is in that answer: the task's `inputs` (spec text, interface
|
|
27
|
+
data), its `skill_texts` (follow them) and its `instructions`. If
|
|
28
|
+
`previous_rejection` is not empty, an earlier attempt was rejected: fix exactly those
|
|
29
|
+
reasons.
|
|
30
|
+
3. You have no tool to read, list or search files, on purpose. Do not try to read the
|
|
31
|
+
RTL or any other file, even if an input or instruction asks you to: the chipgraph
|
|
32
|
+
guard refuses it. If the context is not enough to write the tests, stop and report
|
|
33
|
+
`needs_human` with the question.
|
|
34
|
+
4. Write only the files listed in `outputs`, at the absolute paths in `outputs_abs`.
|
|
35
|
+
Any other write is refused by the chipgraph guard. If a write is refused, do not
|
|
36
|
+
try another path or another tool: note it in your report.
|
|
37
|
+
5. Make the outputs pass the checks the context names. You cannot run them yourself;
|
|
38
|
+
the engine runs them when the task is submitted.
|
|
39
|
+
6. Do not invent requirements or behaviour the spec does not state.
|
|
40
|
+
|
|
41
|
+
Finish with a short report, in this form, and nothing after it:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
status: done | needs_human
|
|
45
|
+
files_written: <paths>
|
|
46
|
+
assumptions: <one per line, or none>
|
|
47
|
+
open_questions: <one per line, or none>
|
|
48
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
# The Triage role (DESIGN.md 5.1, 5.4): classifies a failure and picks the next step. It
|
|
3
|
+
# is not handed out by next_task: the fast decision layer decide() serves it (rules in
|
|
4
|
+
# code, then a small model, then a large one), through the hand-written
|
|
5
|
+
# `chipgraph:decider` subagent in runtime claude-code.
|
|
6
|
+
schema_version: 1
|
|
7
|
+
id: triage
|
|
8
|
+
description: 'chipgraph Triage role (DESIGN 5.1, 5.4): classifies a failure, picks the next step, summarises a log; writes nothing. Served by the fast decision layer decide() (the chipgraph:decider subagent in runtime claude-code), not dispatched through next_task: agent rules cannot use it.'
|
|
9
|
+
default_tier: small
|
|
10
|
+
escalate_to: large
|
|
11
|
+
tools: [engine_decisions]
|
|
12
|
+
write_scope: none
|
|
13
|
+
read_policy:
|
|
14
|
+
mode: any
|
|
15
|
+
shell: false
|
|
16
|
+
dispatch: false
|
|
17
|
+
---
|
|
18
|
+
You are the chipgraph **Triage** role: you answer one multiple-choice question for a
|
|
19
|
+
chip design build (what kind of failure this is, what to do next). The engine asked it;
|
|
20
|
+
you only pick the answer.
|
|
21
|
+
|
|
22
|
+
Your prompt holds everything: the question, its choices and its context. Do not read
|
|
23
|
+
files. (Use {tool:engine_decisions} only if your prompt is missing the question.)
|
|
24
|
+
|
|
25
|
+
- `value` must be copied exactly from the choices.
|
|
26
|
+
- `confidence` is how sure you are, from 0 to 1. Do not overstate it: a low confidence
|
|
27
|
+
sends the question to a stronger model.
|
|
28
|
+
- `reason` is one short sentence that points at what in the context decided it.
|
|
29
|
+
|
|
30
|
+
Reply with a single JSON object and nothing before or after it:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
{"value": "<one of the choices>", "confidence": <0 to 1>, "reason": "<one sentence>"}
|
|
34
|
+
```
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Applying a role's tool table to rules and tasks (DESIGN.md 5.1, 5.4).
|
|
2
|
+
|
|
3
|
+
- `model_ladder`: a task's first tier and escalation tier, from its role unless the rule
|
|
4
|
+
sets its own.
|
|
5
|
+
- `check_agent_rules`: the rule validator for roles: an unknown role, a role agent rules
|
|
6
|
+
may not use, a rule that breaks its role's write scope (a role that writes no files
|
|
7
|
+
may only have the one output the engine writes from its reply, of a kind its
|
|
8
|
+
`engine_writes` names), or a rule that gives a role an input of a kind it must not see
|
|
9
|
+
(by its kind, or by a name the role's `deny_globs` match: `denied_kind`).
|
|
10
|
+
- `denied_reads`: the repo-relative paths and globs a task's agent must not read, from
|
|
11
|
+
its role's read policy resolved against the project (the build graph's artifacts and
|
|
12
|
+
the profile's layout); `task_denied_reads` is the same for a role id and a profile,
|
|
13
|
+
the one function every runtime uses for a task (M2-11a).
|
|
14
|
+
- `path_denied` / `covers_denied`: how a path is matched against those (the runtime's
|
|
15
|
+
guard implements the same rules on its own).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import posixpath
|
|
21
|
+
import re
|
|
22
|
+
from collections.abc import Iterable, Mapping
|
|
23
|
+
from fnmatch import fnmatchcase
|
|
24
|
+
|
|
25
|
+
from chipgraph.core.config.models import Profile
|
|
26
|
+
from chipgraph.core.contracts import ArtifactRef, Budget, ModelTier, RuleInstance, RuleSpec
|
|
27
|
+
from chipgraph.core.runtime.roles.registry import find_role
|
|
28
|
+
from chipgraph.core.runtime.roles.spec import RoleError, RoleSpec
|
|
29
|
+
|
|
30
|
+
LayoutValue = str | tuple[str, ...]
|
|
31
|
+
"""A profile layout entry: one path template, or several (an empty tuple means none)."""
|
|
32
|
+
|
|
33
|
+
_PLACEHOLDER_RE = re.compile(r"\{([^{}]+)\}")
|
|
34
|
+
_GLOB_CHARS = frozenset("*?[{")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def model_ladder(role: RoleSpec | None, budget: Budget) -> tuple[ModelTier, ModelTier | None]:
|
|
38
|
+
"""The task's `(tier, escalate)`: the rule's when its budget sets either, else the role's.
|
|
39
|
+
|
|
40
|
+
The ladder is taken as a whole, so a rule that sets only `tier` does not escalate
|
|
41
|
+
unless it says so. An unknown role (`None`) leaves the budget as it is.
|
|
42
|
+
"""
|
|
43
|
+
if role is None or {"tier", "escalate"} & budget.model_fields_set:
|
|
44
|
+
return budget.tier, budget.escalate
|
|
45
|
+
return role.default_tier, role.escalate_to
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def check_agent_rules(rules: Mapping[str, RuleSpec], instances: Iterable[RuleInstance]) -> None:
|
|
49
|
+
"""Raise `RoleError` listing every agent rule that breaks its role's tool table."""
|
|
50
|
+
problems: list[str] = []
|
|
51
|
+
for rule in sorted(rules.values(), key=lambda r: r.id):
|
|
52
|
+
if rule.kind != "agent" or rule.role is None:
|
|
53
|
+
continue
|
|
54
|
+
role = find_role(rule.role)
|
|
55
|
+
if role is None:
|
|
56
|
+
problems.append(f"rule {rule.id!r}: unknown role {rule.role!r}")
|
|
57
|
+
elif not role.dispatch:
|
|
58
|
+
problems.append(
|
|
59
|
+
f"rule {rule.id!r}: role {role.id!r} is not handed out as an agent task "
|
|
60
|
+
f"({role.description})"
|
|
61
|
+
)
|
|
62
|
+
elif role.write_scope == "none" and not role.engine_writes:
|
|
63
|
+
problems.append(
|
|
64
|
+
f"rule {rule.id!r}: role {role.id!r} writes no files, so it cannot produce "
|
|
65
|
+
"the rule's outputs"
|
|
66
|
+
)
|
|
67
|
+
elif role.write_scope == "none" and len(rule.outputs) != 1:
|
|
68
|
+
problems.append(
|
|
69
|
+
f"rule {rule.id!r}: role {role.id!r} writes no files; the engine writes "
|
|
70
|
+
f"exactly one output from its reply, so the rule must have exactly one "
|
|
71
|
+
f"output (it has {len(rule.outputs)})"
|
|
72
|
+
)
|
|
73
|
+
elif role.write_scope in ("plan", "proposal") and len(rule.outputs) != 1:
|
|
74
|
+
problems.append(
|
|
75
|
+
f"rule {rule.id!r}: role {role.id!r} writes only its {role.write_scope} "
|
|
76
|
+
f"file, so the rule must have exactly one output (it has {len(rule.outputs)})"
|
|
77
|
+
)
|
|
78
|
+
seen: set[str] = set()
|
|
79
|
+
for instance in sorted(instances, key=lambda i: i.instance_id):
|
|
80
|
+
owner = rules.get(instance.rule_id)
|
|
81
|
+
if owner is None or owner.kind != "agent" or owner.role is None:
|
|
82
|
+
continue
|
|
83
|
+
role = find_role(owner.role)
|
|
84
|
+
if role is None:
|
|
85
|
+
continue
|
|
86
|
+
if role.dispatch and role.write_scope == "none" and role.engine_writes:
|
|
87
|
+
for ref in instance.outputs:
|
|
88
|
+
key = f"{owner.id}:out:{ref.kind}"
|
|
89
|
+
if ref.kind not in role.engine_writes and key not in seen:
|
|
90
|
+
seen.add(key)
|
|
91
|
+
kinds = ", ".join(repr(k) for k in role.engine_writes)
|
|
92
|
+
problems.append(
|
|
93
|
+
f"rule {owner.id!r}: role {role.id!r} writes no files, and the engine "
|
|
94
|
+
f"writes only a {kinds} output from its reply, but output "
|
|
95
|
+
f"{ref.path!r} is a {ref.kind!r} artifact"
|
|
96
|
+
)
|
|
97
|
+
policy = role.read_policy
|
|
98
|
+
if policy.mode != "deny":
|
|
99
|
+
continue
|
|
100
|
+
for ref in instance.inputs:
|
|
101
|
+
locator = ref.path or ref.model_key
|
|
102
|
+
key = f"{owner.id}:{locator}"
|
|
103
|
+
if key in seen:
|
|
104
|
+
continue
|
|
105
|
+
kind = denied_kind(role, ref.kind, ref.path)
|
|
106
|
+
if kind is not None:
|
|
107
|
+
seen.add(key)
|
|
108
|
+
problems.append(
|
|
109
|
+
f"rule {owner.id!r}: role {role.id!r} must not see {kind!r} "
|
|
110
|
+
f"artifacts, but input {locator!r} is one"
|
|
111
|
+
)
|
|
112
|
+
if problems:
|
|
113
|
+
raise RoleError("; ".join(problems))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _fill(template: str, block: str | None) -> str:
|
|
117
|
+
"""A layout template as a glob: `{block}`/`{BLOCK}` filled in, any other field `*`."""
|
|
118
|
+
|
|
119
|
+
def _replace(match: re.Match[str]) -> str:
|
|
120
|
+
key = match.group(1)
|
|
121
|
+
if block is not None and key == "block":
|
|
122
|
+
return block
|
|
123
|
+
if block is not None and key == "BLOCK":
|
|
124
|
+
return block.upper()
|
|
125
|
+
return "*"
|
|
126
|
+
|
|
127
|
+
return _PLACEHOLDER_RE.sub(_replace, template)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _templates(value: LayoutValue | None) -> tuple[str, ...]:
|
|
131
|
+
if value is None:
|
|
132
|
+
return ()
|
|
133
|
+
return (value,) if isinstance(value, str) else tuple(value)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def denied_kind(role: RoleSpec, kind: str, path: str | None) -> str | None:
|
|
137
|
+
"""The kind `role` must not see that an artifact of `kind` at `path` is, or `None`.
|
|
138
|
+
|
|
139
|
+
Its own kind when the role denies that kind. Otherwise, when `path` matches one of
|
|
140
|
+
the role's `deny_globs`, the role's first denied kind (or `'denied'`): an artifact's
|
|
141
|
+
kind comes from its suffix, so a testbench `tb_x.sv` is an `rtl` file by kind, and
|
|
142
|
+
only its name says it is a testbench. One rule for every role, whichever kinds it
|
|
143
|
+
denies.
|
|
144
|
+
"""
|
|
145
|
+
policy = role.read_policy
|
|
146
|
+
if policy.mode != "deny":
|
|
147
|
+
return None
|
|
148
|
+
if kind in policy.deny_kinds:
|
|
149
|
+
return kind
|
|
150
|
+
if path is not None and policy.deny_globs:
|
|
151
|
+
clean = _clean(path)
|
|
152
|
+
if clean is not None and path_denied(clean, policy.deny_globs):
|
|
153
|
+
return policy.deny_kinds[0] if policy.deny_kinds else "denied"
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _clean(path: str) -> str | None:
|
|
158
|
+
"""A repo-relative POSIX path or glob, normalised; `None` if it leaves the repo."""
|
|
159
|
+
text = path.replace("\\", "/").strip()
|
|
160
|
+
if not text or text.startswith("/"):
|
|
161
|
+
return None
|
|
162
|
+
norm = posixpath.normpath(text)
|
|
163
|
+
if norm == "." or norm == ".." or norm.startswith("../"):
|
|
164
|
+
return None
|
|
165
|
+
return norm
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def denied_reads(
|
|
169
|
+
role: RoleSpec,
|
|
170
|
+
*,
|
|
171
|
+
artifacts: Iterable[ArtifactRef],
|
|
172
|
+
layout: Mapping[str, LayoutValue],
|
|
173
|
+
block_layouts: Mapping[str, Mapping[str, LayoutValue]] | None = None,
|
|
174
|
+
keep: Iterable[str] = (),
|
|
175
|
+
) -> tuple[str, ...]:
|
|
176
|
+
"""The paths and globs a task of `role` must not read, sorted (empty for `any`).
|
|
177
|
+
|
|
178
|
+
- every artifact of a denied kind among `artifacts` (the build graph's inputs and
|
|
179
|
+
outputs), and the directory holding it (`<dir>/**`, never the project root);
|
|
180
|
+
- the profile's `layout` templates for each denied kind, per block when
|
|
181
|
+
`block_layouts` (block name to its layout overrides) is given, with `{block}`
|
|
182
|
+
filled in and any other field as `*`;
|
|
183
|
+
- the role's own `deny_globs`.
|
|
184
|
+
|
|
185
|
+
Paths in `keep` (the task's own outputs) are never listed.
|
|
186
|
+
"""
|
|
187
|
+
policy = role.read_policy
|
|
188
|
+
if policy.mode == "any":
|
|
189
|
+
return ()
|
|
190
|
+
kinds = set(policy.deny_kinds)
|
|
191
|
+
denied: set[str] = set()
|
|
192
|
+
for ref in artifacts:
|
|
193
|
+
if ref.path is None or ref.kind not in kinds:
|
|
194
|
+
continue
|
|
195
|
+
path = _clean(ref.path)
|
|
196
|
+
if path is None:
|
|
197
|
+
continue
|
|
198
|
+
denied.add(path)
|
|
199
|
+
parent = posixpath.dirname(path)
|
|
200
|
+
if parent:
|
|
201
|
+
denied.add(f"{parent}/**")
|
|
202
|
+
blocks = dict(block_layouts or {})
|
|
203
|
+
for kind in sorted(kinds):
|
|
204
|
+
if blocks:
|
|
205
|
+
for block, overrides in sorted(blocks.items()):
|
|
206
|
+
value = overrides[kind] if kind in overrides else layout.get(kind)
|
|
207
|
+
denied.update(_fill(t, block) for t in _templates(value))
|
|
208
|
+
else:
|
|
209
|
+
denied.update(_fill(t, None) for t in _templates(layout.get(kind)))
|
|
210
|
+
denied.update(policy.deny_globs)
|
|
211
|
+
cleaned = {c for c in (_clean(d) for d in denied) if c is not None}
|
|
212
|
+
return tuple(sorted(cleaned - set(keep)))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def task_denied_reads(
|
|
216
|
+
role_id: str | None,
|
|
217
|
+
*,
|
|
218
|
+
artifacts: Iterable[ArtifactRef],
|
|
219
|
+
profile: Profile,
|
|
220
|
+
keep: Iterable[str] = (),
|
|
221
|
+
) -> tuple[str, ...]:
|
|
222
|
+
"""What a task of `role_id` must not read in the project of `profile` (`denied_reads`).
|
|
223
|
+
|
|
224
|
+
`artifacts` are the build graph's inputs and outputs; the profile's `layout` and every
|
|
225
|
+
block's `layout` override are resolved; `keep` (the task's own outputs) is never
|
|
226
|
+
listed. Empty for an unknown role or one that may read anything.
|
|
227
|
+
"""
|
|
228
|
+
role = find_role(role_id) if role_id is not None else None
|
|
229
|
+
if role is None:
|
|
230
|
+
return ()
|
|
231
|
+
return denied_reads(
|
|
232
|
+
role,
|
|
233
|
+
artifacts=artifacts,
|
|
234
|
+
layout=profile.layout,
|
|
235
|
+
block_layouts={name: block.layout for name, block in profile.blocks.items()},
|
|
236
|
+
keep=keep,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def static_prefix(pattern: str) -> str:
|
|
241
|
+
"""The leading path segments of `pattern` that hold no glob character ('' for none)."""
|
|
242
|
+
parts: list[str] = []
|
|
243
|
+
for part in pattern.split("/"):
|
|
244
|
+
if _GLOB_CHARS & set(part):
|
|
245
|
+
break
|
|
246
|
+
parts.append(part)
|
|
247
|
+
return "/".join(parts)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _is_glob(pattern: str) -> bool:
|
|
251
|
+
return bool(_GLOB_CHARS & set(pattern))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _within(path: str, base: str) -> bool:
|
|
255
|
+
"""`path` is `base` or below it ('' is the project root, above everything)."""
|
|
256
|
+
return base == "" or path == base or path.startswith(base + "/")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def path_denied(path: str, denied: Iterable[str]) -> bool:
|
|
260
|
+
"""Whether reading the file `path` (repo-relative) is denied by one of `denied`.
|
|
261
|
+
|
|
262
|
+
A literal entry denies itself and everything below it; a glob denies what it matches
|
|
263
|
+
(`*` also crosses `/`, so `rtl/*.sv` covers `rtl/sub/x.sv`: the safe side).
|
|
264
|
+
"""
|
|
265
|
+
for entry in denied:
|
|
266
|
+
if _is_glob(entry):
|
|
267
|
+
if fnmatchcase(path, entry):
|
|
268
|
+
return True
|
|
269
|
+
elif _within(path, entry):
|
|
270
|
+
return True
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def covers_denied(scope: str, denied: Iterable[str]) -> bool:
|
|
275
|
+
"""Whether a listing or search rooted at directory `scope` could reach a denied path.
|
|
276
|
+
|
|
277
|
+
True when `scope` holds a denied path (it is an ancestor of one: `''`, the project
|
|
278
|
+
root, holds them all), or lies inside one, or inside the static prefix of a glob.
|
|
279
|
+
"""
|
|
280
|
+
for entry in denied:
|
|
281
|
+
prefix = static_prefix(entry) if _is_glob(entry) else entry
|
|
282
|
+
if _within(prefix, scope) or _within(scope, prefix):
|
|
283
|
+
return True
|
|
284
|
+
if _is_glob(entry) and fnmatchcase(scope, entry):
|
|
285
|
+
return True
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
__all__ = [
|
|
290
|
+
"LayoutValue",
|
|
291
|
+
"check_agent_rules",
|
|
292
|
+
"covers_denied",
|
|
293
|
+
"denied_kind",
|
|
294
|
+
"denied_reads",
|
|
295
|
+
"model_ladder",
|
|
296
|
+
"path_denied",
|
|
297
|
+
"static_prefix",
|
|
298
|
+
"task_denied_reads",
|
|
299
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""The built-in roles: one human-readable file each under `data/` (DESIGN.md 5.1, D8).
|
|
2
|
+
|
|
3
|
+
`get_role(id)` and `list_roles()` read them once, through `importlib.resources`, so the
|
|
4
|
+
same files are used from a checkout and from an installed wheel.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from functools import cache
|
|
10
|
+
from importlib import resources
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from chipgraph.core.runtime.roles.spec import RoleError, RoleSpec, parse_role_file
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def roles_dir() -> Path:
|
|
17
|
+
"""The directory of the built-in role files."""
|
|
18
|
+
root = resources.files("chipgraph.core.runtime.roles") / "data"
|
|
19
|
+
if not isinstance(root, Path) or not root.is_dir():
|
|
20
|
+
raise RoleError("the built-in role files are not on a real filesystem")
|
|
21
|
+
return root
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@cache
|
|
25
|
+
def _roles() -> dict[str, RoleSpec]:
|
|
26
|
+
roles: dict[str, RoleSpec] = {}
|
|
27
|
+
for path in sorted(roles_dir().glob("*.md")):
|
|
28
|
+
role = parse_role_file(path)
|
|
29
|
+
roles[role.id] = role
|
|
30
|
+
return roles
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def list_roles() -> tuple[RoleSpec, ...]:
|
|
34
|
+
"""Every built-in role, sorted by id."""
|
|
35
|
+
return tuple(_roles()[key] for key in sorted(_roles()))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def find_role(role_id: str) -> RoleSpec | None:
|
|
39
|
+
"""The role `role_id` (a `<namespace>/` prefix is ignored), or `None` if unknown."""
|
|
40
|
+
return _roles().get(role_id.rsplit("/", 1)[-1])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_role(role_id: str) -> RoleSpec:
|
|
44
|
+
"""The role `role_id`; raises `RoleError` naming the known roles if there is none."""
|
|
45
|
+
role = find_role(role_id)
|
|
46
|
+
if role is None:
|
|
47
|
+
known = ", ".join(sorted(_roles()))
|
|
48
|
+
raise RoleError(f"unknown role {role_id!r}; the roles are: {known}")
|
|
49
|
+
return role
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = ["find_role", "get_role", "list_roles", "roles_dir"]
|