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,113 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# QNSC naming rule, as data for chipgraph's `naming` check.
|
|
3
|
+
#
|
|
4
|
+
# Translated from QNSC_RTL_Design_Naming_Rule V1.1 as enforced by QSoC
|
|
5
|
+
# `flow/lint/naming_rules.yml` at commit 7a917d1 (repository
|
|
6
|
+
# quynhonsemiconductor/vlsi_deep_training, Apache-2.0). The rule ids and messages
|
|
7
|
+
# keep the source document's section numbering ("2.1 module", "1.5 vocabulary", ...)
|
|
8
|
+
# so a reviewer can compare this file with section 4.3 of the rule document.
|
|
9
|
+
#
|
|
10
|
+
# Unlike the source text checker, chipgraph classifies each identifier by its
|
|
11
|
+
# SystemVerilog syntax node kind (pyslang), so the pattern for a `port` is only ever
|
|
12
|
+
# applied to a real port declaration, never to a same-named signal or a reference.
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
15
|
+
schema_version: 1
|
|
16
|
+
document: QNSC_RTL_Design_Naming_Rule
|
|
17
|
+
version: "1.1"
|
|
18
|
+
ignore_comment: "naming-check: ignore"
|
|
19
|
+
|
|
20
|
+
# -----------------------------------------------------------------------------
|
|
21
|
+
# Allowed-name pattern per object kind (section 2 -- module, instance, RTL).
|
|
22
|
+
# The whole identifier must match the pattern.
|
|
23
|
+
# -----------------------------------------------------------------------------
|
|
24
|
+
identifiers:
|
|
25
|
+
module:
|
|
26
|
+
rule: "2.1 module"
|
|
27
|
+
# A QNSC functional module, a wrapper around an IP, or a generic QNSC module.
|
|
28
|
+
pattern: '^(?:m_qnsc_wrap_[a-z0-9_]+|m_qnsc_[a-z0-9_]+|qnsc_[a-z0-9_]+)$'
|
|
29
|
+
message: "must be m_qnsc_<function>, m_qnsc_wrap_<block> or qnsc_<function>"
|
|
30
|
+
|
|
31
|
+
port:
|
|
32
|
+
rule: "1.2 port prefix"
|
|
33
|
+
pattern: '^(?:i_|o_|io_)[a-z0-9_]+$'
|
|
34
|
+
message: "must start with i_, o_ or io_"
|
|
35
|
+
|
|
36
|
+
parameter:
|
|
37
|
+
rule: "2.4 parameter"
|
|
38
|
+
pattern: '^(?:P_|C_|S_)[A-Z0-9_]+$'
|
|
39
|
+
message: >-
|
|
40
|
+
must be P_<FUNCTION> (parameter), C_<FUNCTION> (constant) or S_<STATE>
|
|
41
|
+
(FSM state), uppercase
|
|
42
|
+
|
|
43
|
+
localparam:
|
|
44
|
+
rule: "2.4 parameter"
|
|
45
|
+
pattern: '^(?:P_|C_|S_)[A-Z0-9_]+$'
|
|
46
|
+
message: >-
|
|
47
|
+
must be P_<FUNCTION> (parameter), C_<FUNCTION> (constant) or S_<STATE>
|
|
48
|
+
(FSM state), uppercase
|
|
49
|
+
|
|
50
|
+
enum_value:
|
|
51
|
+
rule: "2.4 parameter"
|
|
52
|
+
pattern: '^S_[A-Z0-9_]+$'
|
|
53
|
+
message: "an FSM state must be S_<STATE>, uppercase"
|
|
54
|
+
|
|
55
|
+
instance:
|
|
56
|
+
rule: "2.2 instance"
|
|
57
|
+
pattern: '^u_[a-z0-9_]+$'
|
|
58
|
+
message: "must be u_<function>[_<index>]"
|
|
59
|
+
|
|
60
|
+
signal:
|
|
61
|
+
rule: "2.3 signal"
|
|
62
|
+
pattern: '^(?:r_|w_|mem_)[a-z0-9_]+$'
|
|
63
|
+
message: >-
|
|
64
|
+
must be r_<function> if registered, w_<function> if combinational, or
|
|
65
|
+
mem_<function> if a memory array
|
|
66
|
+
|
|
67
|
+
memory:
|
|
68
|
+
rule: "2.3 signal"
|
|
69
|
+
pattern: '^mem_[a-z0-9_]+$'
|
|
70
|
+
message: "a memory array must be mem_<function>"
|
|
71
|
+
|
|
72
|
+
genvar:
|
|
73
|
+
rule: "2.3 signal"
|
|
74
|
+
pattern: '^[a-z][a-z0-9_]*$'
|
|
75
|
+
message: "must be lowercase with _ only"
|
|
76
|
+
|
|
77
|
+
# -----------------------------------------------------------------------------
|
|
78
|
+
# Lexical rules (section 1), applied to every identifier declared in a file.
|
|
79
|
+
# -----------------------------------------------------------------------------
|
|
80
|
+
lexical:
|
|
81
|
+
- rule: "1.1 case"
|
|
82
|
+
forbid_pattern: '[a-z][A-Z]|[A-Z]{2,}[a-z]'
|
|
83
|
+
allow_if_matches: '^(?:P_|C_|S_)[A-Z0-9_]+$' # uppercase constants are fine
|
|
84
|
+
message: >-
|
|
85
|
+
uses mixed case; identifiers are lowercase with _ (uppercase only for P_/C_/S_)
|
|
86
|
+
|
|
87
|
+
- rule: "1.3 active low"
|
|
88
|
+
forbid_pattern: '\b(?:rstn|resetn|reset_b|rst_bar)\b'
|
|
89
|
+
message: "active low is _n after the meaning, as in i_rst_n_sys"
|
|
90
|
+
|
|
91
|
+
- rule: "1.4 index"
|
|
92
|
+
forbid_pattern: '[a-z]\d+(?:_|$)'
|
|
93
|
+
allow_if_matches: '^(?:P_|C_|S_)[A-Z0-9_]+$'
|
|
94
|
+
message: "a numeric index takes an underscore, as in timer_0"
|
|
95
|
+
|
|
96
|
+
# -----------------------------------------------------------------------------
|
|
97
|
+
# Mandatory vocabulary (section 1.5): left is forbidden, right is the project term.
|
|
98
|
+
# -----------------------------------------------------------------------------
|
|
99
|
+
vocabulary:
|
|
100
|
+
rule: "1.5 vocabulary"
|
|
101
|
+
message: "the project term is"
|
|
102
|
+
replace:
|
|
103
|
+
irq: int
|
|
104
|
+
interrupt: int
|
|
105
|
+
clock: clk
|
|
106
|
+
reset: rst
|
|
107
|
+
config: cfg
|
|
108
|
+
debug: dbg
|
|
109
|
+
power: pwr
|
|
110
|
+
memory: mem
|
|
111
|
+
peripheral: peri
|
|
112
|
+
multiplexer: mux
|
|
113
|
+
analog: ana
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# QNSC organization profile (D36): the shared rules a project inherits with
|
|
2
|
+
# `extends: [org:qnsc]`. Kept minimal on purpose -- it points the `naming` check at the
|
|
3
|
+
# organization's data-defined naming rule; a project overrides or adds the rest.
|
|
4
|
+
naming:
|
|
5
|
+
rules: org:qnsc/naming-v1.yml
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Built-in packs shipped with chipgraph (DECISIONS D36).
|
|
2
|
+
|
|
3
|
+
Each pack is one subdirectory with a `pack.yml` manifest and, when it has code, an
|
|
4
|
+
`__init__.py`. Directory names are snake_case (`spec_core/`) so the code is importable;
|
|
5
|
+
the pack name in `pack.yml` keeps its dashes (`spec-core`). Pack code registers through
|
|
6
|
+
the `chipgraph.adapters.<kind>` entry points, like any adapter. `chipgraph.core` must not
|
|
7
|
+
import this package (enforced by import-linter).
|
|
8
|
+
"""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""The ``assist`` pack: whole-project assistance commands (DECISIONS D36).
|
|
2
|
+
|
|
3
|
+
This pack ships ``/audit`` (task M1-20): it runs every deterministic check the project's
|
|
4
|
+
profile configures over the whole project, records their findings in the finding store as
|
|
5
|
+
usual, then builds a report that groups the findings by DESIGN.md 4.8 layer and severity
|
|
6
|
+
(:mod:`chipgraph.packs.assist.audit`). The report is what ``chipgraph audit`` prints and
|
|
7
|
+
the ``audit`` MCP tool returns; ``chipgraph try`` (task M1-21) reuses :func:`run_audit`.
|
|
8
|
+
|
|
9
|
+
The pack carries no code adapters of its own -- it orchestrates the checks other packs
|
|
10
|
+
register -- so, like every built-in pack, its directory is discovered by
|
|
11
|
+
:func:`chipgraph.app.build.builtin_packs_dir`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from chipgraph.packs.assist.audit import AuditReport, run_audit
|
|
15
|
+
|
|
16
|
+
__all__ = ["AuditReport", "run_audit"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""`/ask` (task M1-13): questions about the project, answered only with checked citations.
|
|
2
|
+
|
|
3
|
+
- `ask_context(ctx, question, limit)` (`retrieve`): the sources an answer may use, from
|
|
4
|
+
typed Design Model lookups (`ModelQuery`) and a safe FTS5 search over entities and the
|
|
5
|
+
indexed documents (`fts`). Nothing from an `nda` file.
|
|
6
|
+
- `ask_check(ctx, answer)` (`check`): every citation of an `AskAnswer` must name an indexed
|
|
7
|
+
`path:line` or a `model:<key>`; an answer that is not `unknown` needs at least one.
|
|
8
|
+
- `run_ask` / `answer_question` (`answer`): the API-runtime answerer behind `chipgraph
|
|
9
|
+
ask`, with the same contract and checker. In Claude Code, `/chipgraph:ask` runs the
|
|
10
|
+
`chipgraph:asker` subagent over the `ask_context` and `ask_check` MCP tools instead.
|
|
11
|
+
- `index_documents` (`documents`): the document set `chipgraph ingest` indexes after it
|
|
12
|
+
writes the model store (which drops earlier documents).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from chipgraph.packs.assist.ask.answer import (
|
|
16
|
+
AskResult,
|
|
17
|
+
answer_question,
|
|
18
|
+
make_provider,
|
|
19
|
+
parse_answer,
|
|
20
|
+
run_ask,
|
|
21
|
+
)
|
|
22
|
+
from chipgraph.packs.assist.ask.check import ask_check, check_answer, check_citation
|
|
23
|
+
from chipgraph.packs.assist.ask.contract import (
|
|
24
|
+
UNKNOWN_ANSWER,
|
|
25
|
+
AskAnswer,
|
|
26
|
+
AskCheck,
|
|
27
|
+
AskContext,
|
|
28
|
+
AskSource,
|
|
29
|
+
CitationCheck,
|
|
30
|
+
DocLine,
|
|
31
|
+
)
|
|
32
|
+
from chipgraph.packs.assist.ask.documents import DocumentIndexReport, index_documents
|
|
33
|
+
from chipgraph.packs.assist.ask.fts import fts_query, question_terms
|
|
34
|
+
from chipgraph.packs.assist.ask.retrieve import ask_context, retrieve
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"UNKNOWN_ANSWER",
|
|
38
|
+
"AskAnswer",
|
|
39
|
+
"AskCheck",
|
|
40
|
+
"AskContext",
|
|
41
|
+
"AskResult",
|
|
42
|
+
"AskSource",
|
|
43
|
+
"CitationCheck",
|
|
44
|
+
"DocLine",
|
|
45
|
+
"DocumentIndexReport",
|
|
46
|
+
"answer_question",
|
|
47
|
+
"ask_check",
|
|
48
|
+
"ask_context",
|
|
49
|
+
"check_answer",
|
|
50
|
+
"check_citation",
|
|
51
|
+
"fts_query",
|
|
52
|
+
"index_documents",
|
|
53
|
+
"make_provider",
|
|
54
|
+
"parse_answer",
|
|
55
|
+
"question_terms",
|
|
56
|
+
"retrieve",
|
|
57
|
+
"run_ask",
|
|
58
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Read-only lookups in the documents table of the model store (`ModelStore.add_document`).
|
|
2
|
+
|
|
3
|
+
`ModelStore` offers full-text `search` only; `/ask` also needs exact line lookups (the
|
|
4
|
+
context around a hit, and checking that a cited `file:line` is indexed). These read the
|
|
5
|
+
same `documents(path, line, text)` table, read-only, and treat a missing database or
|
|
6
|
+
table as "no documents".
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DocumentLines:
|
|
17
|
+
"""Line lookups over the indexed documents of the store at `db_path`."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, db_path: Path) -> None:
|
|
20
|
+
self.db_path = db_path
|
|
21
|
+
self._counts: dict[str, int] | None = None
|
|
22
|
+
|
|
23
|
+
def line_counts(self) -> dict[str, int]:
|
|
24
|
+
"""Indexed path -> its number of lines (the highest line number)."""
|
|
25
|
+
if self._counts is None:
|
|
26
|
+
rows = self._query("SELECT path, MAX(line) FROM documents GROUP BY path", ())
|
|
27
|
+
self._counts = {str(path): int(count) for path, count in rows}
|
|
28
|
+
return self._counts
|
|
29
|
+
|
|
30
|
+
def is_indexed(self, path: str) -> bool:
|
|
31
|
+
return path in self.line_counts()
|
|
32
|
+
|
|
33
|
+
def lines(self, path: str, start: int, end: int) -> list[tuple[int, str]]:
|
|
34
|
+
"""The `(line, text)` rows of `path` from `start` to `end`, inclusive, in order."""
|
|
35
|
+
rows = self._query(
|
|
36
|
+
"SELECT line, text FROM documents WHERE path = ? AND line BETWEEN ? AND ? "
|
|
37
|
+
"ORDER BY line",
|
|
38
|
+
(path, start, end),
|
|
39
|
+
)
|
|
40
|
+
return [(int(line), str(text)) for line, text in rows]
|
|
41
|
+
|
|
42
|
+
def _query(self, sql: str, params: tuple[object, ...]) -> list[tuple[Any, ...]]:
|
|
43
|
+
if not self.db_path.is_file():
|
|
44
|
+
return []
|
|
45
|
+
try:
|
|
46
|
+
conn = sqlite3.connect(f"{self.db_path.resolve().as_uri()}?mode=ro", uri=True)
|
|
47
|
+
except sqlite3.Error:
|
|
48
|
+
return []
|
|
49
|
+
try:
|
|
50
|
+
return list(conn.execute(sql, params))
|
|
51
|
+
except sqlite3.Error:
|
|
52
|
+
return []
|
|
53
|
+
finally:
|
|
54
|
+
conn.close()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = ["DocumentLines"]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""The project as `/ask` sees it: the stored Design Model, its indexed documents, labels.
|
|
2
|
+
|
|
3
|
+
`AskProject.load` opens the project's model store (it never ingests: a missing store is
|
|
4
|
+
an error telling the user to run `chipgraph ingest`). Entities read from an `nda` file are
|
|
5
|
+
hidden from `/ask` like `nda` documents are: never shown, never citable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from chipgraph.app.context import AppContext
|
|
15
|
+
from chipgraph.app.errors import AppError
|
|
16
|
+
from chipgraph.core.model import (
|
|
17
|
+
DesignModel,
|
|
18
|
+
ModelQuery,
|
|
19
|
+
ModelStore,
|
|
20
|
+
QueryError,
|
|
21
|
+
default_model_db_path,
|
|
22
|
+
)
|
|
23
|
+
from chipgraph.core.model.entities import EntityBase
|
|
24
|
+
from chipgraph.core.state.artifacts import LabelRules
|
|
25
|
+
from chipgraph.packs.assist.ask._db import DocumentLines
|
|
26
|
+
|
|
27
|
+
_HEX_FIELDS = frozenset({"offset", "base", "address"})
|
|
28
|
+
_SKIP_FIELDS = frozenset({"schema_version", "key", "kind", "name", "source"})
|
|
29
|
+
_MAX_LIST = 24
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class AskProject:
|
|
34
|
+
"""The stored model, a query API on it, the indexed documents and the data labels."""
|
|
35
|
+
|
|
36
|
+
root: Path
|
|
37
|
+
store: ModelStore
|
|
38
|
+
model: DesignModel
|
|
39
|
+
query: ModelQuery
|
|
40
|
+
docs: DocumentLines
|
|
41
|
+
labels: LabelRules
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def load(cls, ctx: AppContext) -> AskProject:
|
|
45
|
+
"""Open the project's model store; `AppError` if there is none yet."""
|
|
46
|
+
db_path = default_model_db_path(ctx.root)
|
|
47
|
+
if not db_path.is_file():
|
|
48
|
+
raise AppError("no Design Model yet: run `chipgraph ingest`")
|
|
49
|
+
store = ModelStore(db_path)
|
|
50
|
+
model = store.read()
|
|
51
|
+
return cls(
|
|
52
|
+
root=ctx.root,
|
|
53
|
+
store=store,
|
|
54
|
+
model=model,
|
|
55
|
+
query=ModelQuery(model, store),
|
|
56
|
+
docs=DocumentLines(db_path),
|
|
57
|
+
labels=ctx.store.labels,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# --- visibility -------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def visible(self, entity: EntityBase) -> bool:
|
|
63
|
+
"""False for an entity read from an `nda` file: `/ask` never shows or cites it."""
|
|
64
|
+
file = entity.source.file
|
|
65
|
+
return not (file and self.labels.label_for(file) == "nda")
|
|
66
|
+
|
|
67
|
+
def get(self, key: str) -> EntityBase | None:
|
|
68
|
+
"""The visible entity `key`, or None."""
|
|
69
|
+
entity = self.model.get(key)
|
|
70
|
+
return entity if entity is not None and self.visible(entity) else None
|
|
71
|
+
|
|
72
|
+
# --- entity facts -----------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def block_of(self, entity: EntityBase) -> str | None:
|
|
75
|
+
"""The block key an entity belongs to, from its typed fields, attrs or owner."""
|
|
76
|
+
block = getattr(entity, "block", None)
|
|
77
|
+
if isinstance(block, str) and block:
|
|
78
|
+
return block
|
|
79
|
+
attr = entity.attrs.get("block")
|
|
80
|
+
if isinstance(attr, str) and attr:
|
|
81
|
+
return attr
|
|
82
|
+
if entity.kind == "block":
|
|
83
|
+
return entity.key
|
|
84
|
+
for owner_field in ("module", "register"):
|
|
85
|
+
owner_key = getattr(entity, owner_field, None)
|
|
86
|
+
if isinstance(owner_key, str) and owner_key:
|
|
87
|
+
owner = self.model.get(owner_key)
|
|
88
|
+
if owner is not None:
|
|
89
|
+
return self.block_of(owner)
|
|
90
|
+
# Keys like `open_item:<block>.<id>` or `register:<block>.<name>`.
|
|
91
|
+
_, _, rest = entity.key.partition(":")
|
|
92
|
+
head, dot, _ = rest.partition(".")
|
|
93
|
+
if dot and f"block:{head}" in self.model.entities:
|
|
94
|
+
return f"block:{head}"
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def blocks_of(self, entity: EntityBase) -> set[str]:
|
|
98
|
+
"""Every block an entity belongs to: `block_of`, plus an `attrs.blocks` name list."""
|
|
99
|
+
blocks = {b for b in (self.block_of(entity),) if b}
|
|
100
|
+
names = entity.attrs.get("blocks")
|
|
101
|
+
if isinstance(names, list):
|
|
102
|
+
blocks |= {f"block:{n}" for n in names if isinstance(n, str) and n}
|
|
103
|
+
return blocks
|
|
104
|
+
|
|
105
|
+
def module_of(self, entity: EntityBase) -> str | None:
|
|
106
|
+
module = getattr(entity, "module", None)
|
|
107
|
+
if isinstance(module, str) and module:
|
|
108
|
+
return module
|
|
109
|
+
return entity.key if entity.kind == "module" else None
|
|
110
|
+
|
|
111
|
+
def defined_at(self, entity: EntityBase) -> str | None:
|
|
112
|
+
"""`path:line` where the entity was read from, when that file is indexed."""
|
|
113
|
+
file, line = entity.source.file, entity.source.line
|
|
114
|
+
if file and line and self.docs.is_indexed(file):
|
|
115
|
+
return f"{file}:{line}"
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def summary(self, entity: EntityBase) -> str:
|
|
119
|
+
"""A one-line, factual summary of an entity, from its typed fields and relations."""
|
|
120
|
+
parts = [f"{entity.kind} {entity.name}"]
|
|
121
|
+
facts = _fields(entity)
|
|
122
|
+
extra = self._relations_summary(entity)
|
|
123
|
+
body = "; ".join(f"{k}={_fmt(k, v)}" for k, v in facts.items())
|
|
124
|
+
if extra:
|
|
125
|
+
body = f"{body}; {extra}" if body else extra
|
|
126
|
+
return f"{parts[0]}: {body}" if body else parts[0]
|
|
127
|
+
|
|
128
|
+
def _relations_summary(self, entity: EntityBase) -> str:
|
|
129
|
+
try:
|
|
130
|
+
if entity.kind == "block":
|
|
131
|
+
return self._block_summary(entity)
|
|
132
|
+
if entity.kind == "module":
|
|
133
|
+
return self._module_summary(entity)
|
|
134
|
+
if entity.kind == "requirement":
|
|
135
|
+
trace = self.query.trace(entity.key)
|
|
136
|
+
bits = []
|
|
137
|
+
verifies = self._visible_keys(trace.verifies)
|
|
138
|
+
implements = self._visible_keys(trace.implements)
|
|
139
|
+
if verifies:
|
|
140
|
+
bits.append(f"verified_by={_names(verifies)}")
|
|
141
|
+
if implements:
|
|
142
|
+
bits.append(f"implemented_by={_names(implements)}")
|
|
143
|
+
return "; ".join(bits)
|
|
144
|
+
except QueryError:
|
|
145
|
+
return ""
|
|
146
|
+
children = self._visible_keys(
|
|
147
|
+
[r.dst for r in self.model.relations if r.src == entity.key and r.kind == "contains"]
|
|
148
|
+
)
|
|
149
|
+
if children:
|
|
150
|
+
return f"contains={_names(children)}"
|
|
151
|
+
return ""
|
|
152
|
+
|
|
153
|
+
def _block_summary(self, block: EntityBase) -> str:
|
|
154
|
+
info = self.query.block(block.name)
|
|
155
|
+
members: dict[str, list[str]] = {}
|
|
156
|
+
for entity in self.model.entities.values():
|
|
157
|
+
if entity.kind in ("block", "port", "field") or not self.visible(entity):
|
|
158
|
+
continue
|
|
159
|
+
if self.block_of(entity) == block.key:
|
|
160
|
+
members.setdefault(entity.kind, []).append(entity.key)
|
|
161
|
+
for kind, keys in (("module", info.modules), ("clock", info.clocks)):
|
|
162
|
+
for key in self._visible_keys(keys):
|
|
163
|
+
if key not in members.setdefault(kind, []):
|
|
164
|
+
members[kind].append(key)
|
|
165
|
+
bits = [f"{kind}s={_names(sorted(keys))}" for kind, keys in sorted(members.items())]
|
|
166
|
+
resets = self._visible_keys(info.resets)
|
|
167
|
+
if resets:
|
|
168
|
+
bits.append(f"resets={_names(resets)}")
|
|
169
|
+
return "; ".join(bits)
|
|
170
|
+
|
|
171
|
+
def _module_summary(self, module: EntityBase) -> str:
|
|
172
|
+
info = self.query.module(module.name)
|
|
173
|
+
ports = self._visible_keys(info.ports)
|
|
174
|
+
parameters = self._visible_keys(info.parameters)
|
|
175
|
+
instances = [i for i in info.instances if self.get(i["module_key"]) is not None]
|
|
176
|
+
parents = [i for i in info.instantiated_by if self.get(i["module_key"]) is not None]
|
|
177
|
+
bits = [f"ports={_names(ports)}"] if ports else []
|
|
178
|
+
if parameters:
|
|
179
|
+
bits.append(f"parameters={_names(parameters)}")
|
|
180
|
+
if instances:
|
|
181
|
+
bits.append(
|
|
182
|
+
"instances="
|
|
183
|
+
+ ", ".join(f"{i['instance_name']} ({i['module_key']})" for i in instances)
|
|
184
|
+
)
|
|
185
|
+
if parents:
|
|
186
|
+
bits.append(
|
|
187
|
+
"instantiated_by="
|
|
188
|
+
+ ", ".join(f"{i['module_key']} as {i['instance_name']}" for i in parents)
|
|
189
|
+
)
|
|
190
|
+
return "; ".join(bits)
|
|
191
|
+
|
|
192
|
+
def _visible_keys(self, keys: list[str]) -> list[str]:
|
|
193
|
+
"""`keys` without the entities `/ask` may not show (unknown ones are kept)."""
|
|
194
|
+
return [k for k in keys if (e := self.model.get(k)) is None or self.visible(e)]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _fields(entity: EntityBase) -> dict[str, object]:
|
|
198
|
+
data = entity.model_dump(mode="json")
|
|
199
|
+
out: dict[str, object] = {}
|
|
200
|
+
for key, value in data.items():
|
|
201
|
+
if key in _SKIP_FIELDS or value in (None, "", [], {}):
|
|
202
|
+
continue
|
|
203
|
+
if key == "attrs":
|
|
204
|
+
for attr_key, attr_value in value.items():
|
|
205
|
+
if attr_value not in (None, "", [], {}):
|
|
206
|
+
out[attr_key] = attr_value
|
|
207
|
+
continue
|
|
208
|
+
out[key] = value
|
|
209
|
+
return out
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _fmt(key: str, value: object) -> str:
|
|
213
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
214
|
+
if key in _HEX_FIELDS:
|
|
215
|
+
return f"{value:#x}"
|
|
216
|
+
if value >= 0x100:
|
|
217
|
+
return f"{value} ({value:#x})"
|
|
218
|
+
return str(value)
|
|
219
|
+
if isinstance(value, str):
|
|
220
|
+
return value
|
|
221
|
+
return json.dumps(value)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _names(keys: list[str]) -> str:
|
|
225
|
+
shown = keys[:_MAX_LIST]
|
|
226
|
+
more = f" (+{len(keys) - len(shown)} more)" if len(keys) > len(shown) else ""
|
|
227
|
+
return "[" + ", ".join(shown) + "]" + more
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__all__ = ["AskProject"]
|