chipgraph 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. chipgraph/__init__.py +7 -0
  2. chipgraph/adapters/__init__.py +1 -0
  3. chipgraph/adapters/format/__init__.py +6 -0
  4. chipgraph/adapters/format/__main__.py +16 -0
  5. chipgraph/adapters/format/_yaml_lines.py +54 -0
  6. chipgraph/adapters/format/chip_yaml.py +323 -0
  7. chipgraph/adapters/format/qsoc_contract.py +246 -0
  8. chipgraph/adapters/llm/__init__.py +58 -0
  9. chipgraph/adapters/llm/_budget.py +173 -0
  10. chipgraph/adapters/llm/_errors.py +50 -0
  11. chipgraph/adapters/llm/_nda.py +30 -0
  12. chipgraph/adapters/llm/_retry.py +85 -0
  13. chipgraph/adapters/llm/anthropic.py +247 -0
  14. chipgraph/adapters/llm/decide_backend.py +179 -0
  15. chipgraph/adapters/llm/fake.py +87 -0
  16. chipgraph/adapters/llm/guard.py +68 -0
  17. chipgraph/adapters/parser/__init__.py +22 -0
  18. chipgraph/adapters/parser/cocotb.py +304 -0
  19. chipgraph/adapters/parser/generic_regex.py +75 -0
  20. chipgraph/adapters/parser/verible.py +55 -0
  21. chipgraph/adapters/parser/verilator.py +46 -0
  22. chipgraph/adapters/review/__init__.py +7 -0
  23. chipgraph/adapters/review/file.py +123 -0
  24. chipgraph/adapters/review/github.py +886 -0
  25. chipgraph/adapters/runner/__init__.py +7 -0
  26. chipgraph/adapters/runner/local.py +122 -0
  27. chipgraph/adapters/runtime/__init__.py +5 -0
  28. chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
  29. chipgraph/adapters/runtime/claude_code/agents.py +158 -0
  30. chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
  31. chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
  32. chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
  33. chipgraph/adapters/runtime/claude_code/loop.py +342 -0
  34. chipgraph/adapters/runtime/claude_code/previous.py +419 -0
  35. chipgraph/adapters/runtime/claude_code/review.py +492 -0
  36. chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
  37. chipgraph/adapters/runtime/claude_code/service.py +2285 -0
  38. chipgraph/adapters/tool/__init__.py +7 -0
  39. chipgraph/adapters/tool/cmd.py +229 -0
  40. chipgraph/adapters/tool/edalize.py +773 -0
  41. chipgraph/adapters/tool/filelist.py +259 -0
  42. chipgraph/adapters/tool/pyslang.py +485 -0
  43. chipgraph/adapters/vcs/__init__.py +1 -0
  44. chipgraph/adapters/vcs/git.py +521 -0
  45. chipgraph/app/__init__.py +10 -0
  46. chipgraph/app/baseline.py +339 -0
  47. chipgraph/app/build.py +332 -0
  48. chipgraph/app/checks.py +202 -0
  49. chipgraph/app/context.py +191 -0
  50. chipgraph/app/errors.py +11 -0
  51. chipgraph/app/executors.py +79 -0
  52. chipgraph/app/findings.py +203 -0
  53. chipgraph/app/ingest.py +523 -0
  54. chipgraph/checks/__init__.py +52 -0
  55. chipgraph/checks/_cdc_analyze.py +272 -0
  56. chipgraph/checks/_cdc_elaborate.py +455 -0
  57. chipgraph/checks/_common.py +268 -0
  58. chipgraph/checks/_cross.py +96 -0
  59. chipgraph/checks/_model.py +64 -0
  60. chipgraph/checks/_naming_pyslang.py +235 -0
  61. chipgraph/checks/_naming_rules.py +127 -0
  62. chipgraph/checks/_xref_syntax.py +260 -0
  63. chipgraph/checks/cdc_struct.py +235 -0
  64. chipgraph/checks/connect.py +353 -0
  65. chipgraph/checks/cross_chip.py +454 -0
  66. chipgraph/checks/duplicate.py +153 -0
  67. chipgraph/checks/filelist.py +323 -0
  68. chipgraph/checks/format.py +268 -0
  69. chipgraph/checks/generated.py +151 -0
  70. chipgraph/checks/hardcode.py +233 -0
  71. chipgraph/checks/layout.py +155 -0
  72. chipgraph/checks/naming.py +311 -0
  73. chipgraph/checks/plan_check.py +901 -0
  74. chipgraph/checks/ports_diff.py +203 -0
  75. chipgraph/checks/spec_schema.py +277 -0
  76. chipgraph/checks/style.py +910 -0
  77. chipgraph/checks/tb_static.py +599 -0
  78. chipgraph/checks/trace.py +224 -0
  79. chipgraph/checks/vendor_guard.py +369 -0
  80. chipgraph/checks/wrap_check.py +486 -0
  81. chipgraph/cli/__init__.py +1688 -0
  82. chipgraph/cli/next_steps.py +137 -0
  83. chipgraph/core/__init__.py +1 -0
  84. chipgraph/core/config/__init__.py +93 -0
  85. chipgraph/core/config/defaults.py +14 -0
  86. chipgraph/core/config/errors.py +32 -0
  87. chipgraph/core/config/loader.py +766 -0
  88. chipgraph/core/config/models.py +845 -0
  89. chipgraph/core/config/templates.py +340 -0
  90. chipgraph/core/contracts/__init__.py +75 -0
  91. chipgraph/core/contracts/_paths.py +26 -0
  92. chipgraph/core/contracts/agent.py +49 -0
  93. chipgraph/core/contracts/artifact.py +68 -0
  94. chipgraph/core/contracts/check.py +76 -0
  95. chipgraph/core/contracts/event.py +70 -0
  96. chipgraph/core/contracts/export.py +128 -0
  97. chipgraph/core/contracts/finding.py +171 -0
  98. chipgraph/core/contracts/gate.py +50 -0
  99. chipgraph/core/contracts/rule.py +177 -0
  100. chipgraph/core/contracts/types.py +45 -0
  101. chipgraph/core/engine/__init__.py +70 -0
  102. chipgraph/core/engine/agent_rule.py +1362 -0
  103. chipgraph/core/engine/baseline.py +283 -0
  104. chipgraph/core/engine/decide.py +462 -0
  105. chipgraph/core/engine/fanout.py +1358 -0
  106. chipgraph/core/engine/gate.py +170 -0
  107. chipgraph/core/engine/graph.py +647 -0
  108. chipgraph/core/engine/records.py +60 -0
  109. chipgraph/core/engine/rules.py +92 -0
  110. chipgraph/core/engine/scheduler.py +637 -0
  111. chipgraph/core/engine/select.py +1239 -0
  112. chipgraph/core/engine/targets.py +342 -0
  113. chipgraph/core/model/__init__.py +98 -0
  114. chipgraph/core/model/entities.py +244 -0
  115. chipgraph/core/model/export.py +16 -0
  116. chipgraph/core/model/ingest.py +693 -0
  117. chipgraph/core/model/json_value.py +4 -0
  118. chipgraph/core/model/keys.py +95 -0
  119. chipgraph/core/model/model.py +153 -0
  120. chipgraph/core/model/provenance.py +27 -0
  121. chipgraph/core/model/query.py +583 -0
  122. chipgraph/core/model/registry.py +80 -0
  123. chipgraph/core/model/relations.py +61 -0
  124. chipgraph/core/model/store.py +279 -0
  125. chipgraph/core/plugin_api/__init__.py +81 -0
  126. chipgraph/core/plugin_api/local.py +167 -0
  127. chipgraph/core/plugin_api/pack.py +159 -0
  128. chipgraph/core/plugin_api/protocols.py +177 -0
  129. chipgraph/core/plugin_api/registry.py +111 -0
  130. chipgraph/core/plugin_api/types.py +94 -0
  131. chipgraph/core/runtime/__init__.py +41 -0
  132. chipgraph/core/runtime/executor.py +79 -0
  133. chipgraph/core/runtime/queue.py +565 -0
  134. chipgraph/core/runtime/roles/__init__.py +91 -0
  135. chipgraph/core/runtime/roles/data/author.md +41 -0
  136. chipgraph/core/runtime/roles/data/critic.md +56 -0
  137. chipgraph/core/runtime/roles/data/planner.md +38 -0
  138. chipgraph/core/runtime/roles/data/researcher.md +37 -0
  139. chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
  140. chipgraph/core/runtime/roles/data/tb-author.md +48 -0
  141. chipgraph/core/runtime/roles/data/triage.md +34 -0
  142. chipgraph/core/runtime/roles/policy.py +299 -0
  143. chipgraph/core/runtime/roles/registry.py +52 -0
  144. chipgraph/core/runtime/roles/review.py +275 -0
  145. chipgraph/core/runtime/roles/schema.py +82 -0
  146. chipgraph/core/runtime/roles/skills.py +134 -0
  147. chipgraph/core/runtime/roles/spec.py +232 -0
  148. chipgraph/core/state/__init__.py +1 -0
  149. chipgraph/core/state/artifacts.py +156 -0
  150. chipgraph/core/state/backend.py +117 -0
  151. chipgraph/core/state/findings.py +151 -0
  152. chipgraph/core/state/handoff.py +411 -0
  153. chipgraph/core/state/idempotency.py +59 -0
  154. chipgraph/core/state/journal.py +238 -0
  155. chipgraph/core/state/layout.py +82 -0
  156. chipgraph/core/state/lock.py +195 -0
  157. chipgraph/core/state/trace.py +225 -0
  158. chipgraph/learn/__init__.py +96 -0
  159. chipgraph/learn/draft.py +187 -0
  160. chipgraph/learn/infer.py +580 -0
  161. chipgraph/learn/models.py +145 -0
  162. chipgraph/learn/try_run.py +228 -0
  163. chipgraph/mcp/__init__.py +12 -0
  164. chipgraph/mcp/model_tools.py +167 -0
  165. chipgraph/mcp/server.py +787 -0
  166. chipgraph/orgs/qnsc/naming-v1.yml +113 -0
  167. chipgraph/orgs/qnsc/profile.yml +5 -0
  168. chipgraph/packs/__init__.py +8 -0
  169. chipgraph/packs/assist/__init__.py +16 -0
  170. chipgraph/packs/assist/ask/__init__.py +58 -0
  171. chipgraph/packs/assist/ask/_db.py +57 -0
  172. chipgraph/packs/assist/ask/_project.py +230 -0
  173. chipgraph/packs/assist/ask/answer.py +266 -0
  174. chipgraph/packs/assist/ask/check.py +182 -0
  175. chipgraph/packs/assist/ask/contract.py +115 -0
  176. chipgraph/packs/assist/ask/documents.py +157 -0
  177. chipgraph/packs/assist/ask/fts.py +80 -0
  178. chipgraph/packs/assist/ask/retrieve.py +331 -0
  179. chipgraph/packs/assist/audit/__init__.py +383 -0
  180. chipgraph/packs/assist/pack.yml +13 -0
  181. chipgraph/packs/assist/triage/__init__.py +64 -0
  182. chipgraph/packs/assist/triage/contract.py +94 -0
  183. chipgraph/packs/assist/triage/parse.py +371 -0
  184. chipgraph/packs/assist/triage/question.py +384 -0
  185. chipgraph/packs/assist/triage/report.py +160 -0
  186. chipgraph/packs/assist/triage/rules.py +447 -0
  187. chipgraph/packs/assist/triage/run.py +224 -0
  188. chipgraph/packs/digital_rtl/__init__.py +19 -0
  189. chipgraph/packs/digital_rtl/layout.py +144 -0
  190. chipgraph/packs/digital_rtl/pack.yml +25 -0
  191. chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
  192. chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
  193. chipgraph/packs/digital_rtl/plan/context.py +82 -0
  194. chipgraph/packs/digital_rtl/plan/expand.py +94 -0
  195. chipgraph/packs/digital_rtl/plan/model.py +150 -0
  196. chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
  197. chipgraph/packs/digital_rtl/plan/show.py +176 -0
  198. chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
  199. chipgraph/packs/digital_rtl/pr/body.py +165 -0
  200. chipgraph/packs/digital_rtl/pr/gate.py +110 -0
  201. chipgraph/packs/digital_rtl/pr/open.py +397 -0
  202. chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
  203. chipgraph/packs/digital_rtl/rtl/context.py +272 -0
  204. chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
  205. chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
  206. chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
  207. chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
  208. chipgraph/packs/digital_rtl/rules/review.yml +31 -0
  209. chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
  210. chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
  211. chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
  212. chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
  213. chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
  214. chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
  215. chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
  216. chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
  217. chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
  218. chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
  219. chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
  220. chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
  221. chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
  222. chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
  223. chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
  224. chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
  225. chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
  226. chipgraph/packs/dv/__init__.py +15 -0
  227. chipgraph/packs/dv/declaration.py +392 -0
  228. chipgraph/packs/dv/interface.py +596 -0
  229. chipgraph/packs/dv/pack.yml +14 -0
  230. chipgraph/packs/dv/rules/tb_module.yml +42 -0
  231. chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
  232. chipgraph/packs/spec_core/__init__.py +9 -0
  233. chipgraph/packs/spec_core/extract/__init__.py +12 -0
  234. chipgraph/packs/spec_core/extract/_markdown.py +371 -0
  235. chipgraph/packs/spec_core/extract/_text.py +116 -0
  236. chipgraph/packs/spec_core/extract/mas.py +975 -0
  237. chipgraph/packs/spec_core/gen/__init__.py +11 -0
  238. chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
  239. chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
  240. chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
  241. chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
  242. chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
  243. chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
  244. chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
  245. chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
  246. chipgraph/packs/spec_core/pack.yml +6 -0
  247. chipgraph/py.typed +0 -0
  248. chipgraph-0.1.0.dist-info/METADATA +53 -0
  249. chipgraph-0.1.0.dist-info/RECORD +252 -0
  250. chipgraph-0.1.0.dist-info/WHEEL +4 -0
  251. chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
  252. chipgraph-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,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"]