graphitect 0.2.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 (336) hide show
  1. graphify/__init__.py +30 -0
  2. graphify/__main__.py +757 -0
  3. graphify/_minhash.py +107 -0
  4. graphify/affected.py +318 -0
  5. graphify/always_on/agents-md.md +12 -0
  6. graphify/always_on/antigravity-rules.md +14 -0
  7. graphify/always_on/claude-md.md +9 -0
  8. graphify/always_on/gemini-md.md +9 -0
  9. graphify/always_on/kiro-steering.md +5 -0
  10. graphify/always_on/vscode-instructions.md +17 -0
  11. graphify/analyze.py +769 -0
  12. graphify/benchmark.py +152 -0
  13. graphify/build.py +2300 -0
  14. graphify/cache.py +1746 -0
  15. graphify/callflow_html.py +2051 -0
  16. graphify/cargo_introspect.py +109 -0
  17. graphify/cli.py +4745 -0
  18. graphify/cluster.py +409 -0
  19. graphify/command-kilo.md +15 -0
  20. graphify/cross_repo_calls.py +216 -0
  21. graphify/cross_repo_types.py +75 -0
  22. graphify/csharp_dispatch.py +154 -0
  23. graphify/dedup.py +1213 -0
  24. graphify/detect.py +2566 -0
  25. graphify/diagnostics.py +406 -0
  26. graphify/export.py +1349 -0
  27. graphify/exporters/__init__.py +1 -0
  28. graphify/exporters/base.py +14 -0
  29. graphify/exporters/graphdb.py +173 -0
  30. graphify/exporters/html.py +637 -0
  31. graphify/extract.py +7856 -0
  32. graphify/extractors/MIGRATION.md +107 -0
  33. graphify/extractors/__init__.py +66 -0
  34. graphify/extractors/apex.py +215 -0
  35. graphify/extractors/base.py +85 -0
  36. graphify/extractors/bash.py +579 -0
  37. graphify/extractors/blade.py +53 -0
  38. graphify/extractors/commonlisp.py +540 -0
  39. graphify/extractors/csharp.py +448 -0
  40. graphify/extractors/dart.py +564 -0
  41. graphify/extractors/dm.py +494 -0
  42. graphify/extractors/elixir.py +241 -0
  43. graphify/extractors/engine.py +6509 -0
  44. graphify/extractors/fortran.py +311 -0
  45. graphify/extractors/go.py +527 -0
  46. graphify/extractors/json_config.py +240 -0
  47. graphify/extractors/julia.py +289 -0
  48. graphify/extractors/markdown.py +408 -0
  49. graphify/extractors/models.py +131 -0
  50. graphify/extractors/objc.py +566 -0
  51. graphify/extractors/ocaml.py +289 -0
  52. graphify/extractors/pascal.py +688 -0
  53. graphify/extractors/pascal_forms.py +196 -0
  54. graphify/extractors/powershell.py +522 -0
  55. graphify/extractors/razor.py +192 -0
  56. graphify/extractors/resolution.py +3584 -0
  57. graphify/extractors/robot.py +296 -0
  58. graphify/extractors/rust.py +470 -0
  59. graphify/extractors/sln.py +92 -0
  60. graphify/extractors/sql.py +720 -0
  61. graphify/extractors/terraform.py +181 -0
  62. graphify/extractors/verilog.py +329 -0
  63. graphify/extractors/zig.py +181 -0
  64. graphify/file_slice.py +246 -0
  65. graphify/global_graph.py +194 -0
  66. graphify/google_workspace.py +237 -0
  67. graphify/hooks.py +933 -0
  68. graphify/ids.py +93 -0
  69. graphify/ingest.py +358 -0
  70. graphify/install.py +2366 -0
  71. graphify/llm.py +3544 -0
  72. graphify/manifest.py +4 -0
  73. graphify/manifest_ingest.py +311 -0
  74. graphify/mcp_ingest.py +386 -0
  75. graphify/multigraph_compat.py +212 -0
  76. graphify/pascal_resolution.py +129 -0
  77. graphify/paths.py +436 -0
  78. graphify/pg_introspect.py +165 -0
  79. graphify/prs.py +770 -0
  80. graphify/querylog.py +80 -0
  81. graphify/reflect.py +882 -0
  82. graphify/report.py +346 -0
  83. graphify/resolver_registry.py +85 -0
  84. graphify/ruby_resolution.py +242 -0
  85. graphify/scip_ingest.py +363 -0
  86. graphify/security.py +460 -0
  87. graphify/semantic_cleanup.py +336 -0
  88. graphify/serve.py +2608 -0
  89. graphify/skill-agents.md +710 -0
  90. graphify/skill-aider.md +1283 -0
  91. graphify/skill-amp.md +710 -0
  92. graphify/skill-claw.md +713 -0
  93. graphify/skill-codex.md +710 -0
  94. graphify/skill-copilot.md +713 -0
  95. graphify/skill-devin.md +1410 -0
  96. graphify/skill-droid.md +710 -0
  97. graphify/skill-kilo.md +722 -0
  98. graphify/skill-kiro.md +713 -0
  99. graphify/skill-opencode.md +705 -0
  100. graphify/skill-pi.md +713 -0
  101. graphify/skill-trae.md +711 -0
  102. graphify/skill-vscode.md +709 -0
  103. graphify/skill-windows.md +755 -0
  104. graphify/skill.md +713 -0
  105. graphify/skills/agents/references/add-watch.md +56 -0
  106. graphify/skills/agents/references/exports.md +87 -0
  107. graphify/skills/agents/references/extraction-spec.md +70 -0
  108. graphify/skills/agents/references/github-and-merge.md +46 -0
  109. graphify/skills/agents/references/hooks.md +33 -0
  110. graphify/skills/agents/references/query.md +311 -0
  111. graphify/skills/agents/references/transcribe.md +52 -0
  112. graphify/skills/agents/references/update.md +210 -0
  113. graphify/skills/amp/references/add-watch.md +56 -0
  114. graphify/skills/amp/references/exports.md +87 -0
  115. graphify/skills/amp/references/extraction-spec.md +70 -0
  116. graphify/skills/amp/references/github-and-merge.md +46 -0
  117. graphify/skills/amp/references/hooks.md +33 -0
  118. graphify/skills/amp/references/query.md +311 -0
  119. graphify/skills/amp/references/transcribe.md +52 -0
  120. graphify/skills/amp/references/update.md +210 -0
  121. graphify/skills/claude/references/add-watch.md +56 -0
  122. graphify/skills/claude/references/exports.md +87 -0
  123. graphify/skills/claude/references/extraction-spec.md +70 -0
  124. graphify/skills/claude/references/github-and-merge.md +46 -0
  125. graphify/skills/claude/references/hooks.md +33 -0
  126. graphify/skills/claude/references/query.md +311 -0
  127. graphify/skills/claude/references/transcribe.md +52 -0
  128. graphify/skills/claude/references/update.md +210 -0
  129. graphify/skills/claw/references/add-watch.md +56 -0
  130. graphify/skills/claw/references/exports.md +87 -0
  131. graphify/skills/claw/references/extraction-spec.md +31 -0
  132. graphify/skills/claw/references/github-and-merge.md +46 -0
  133. graphify/skills/claw/references/hooks.md +33 -0
  134. graphify/skills/claw/references/query.md +311 -0
  135. graphify/skills/claw/references/transcribe.md +52 -0
  136. graphify/skills/claw/references/update.md +210 -0
  137. graphify/skills/codex/references/add-watch.md +56 -0
  138. graphify/skills/codex/references/exports.md +87 -0
  139. graphify/skills/codex/references/extraction-spec.md +31 -0
  140. graphify/skills/codex/references/github-and-merge.md +46 -0
  141. graphify/skills/codex/references/hooks.md +33 -0
  142. graphify/skills/codex/references/query.md +311 -0
  143. graphify/skills/codex/references/transcribe.md +52 -0
  144. graphify/skills/codex/references/update.md +210 -0
  145. graphify/skills/copilot/references/add-watch.md +56 -0
  146. graphify/skills/copilot/references/exports.md +87 -0
  147. graphify/skills/copilot/references/extraction-spec.md +70 -0
  148. graphify/skills/copilot/references/github-and-merge.md +46 -0
  149. graphify/skills/copilot/references/hooks.md +33 -0
  150. graphify/skills/copilot/references/query.md +311 -0
  151. graphify/skills/copilot/references/transcribe.md +52 -0
  152. graphify/skills/copilot/references/update.md +210 -0
  153. graphify/skills/droid/references/add-watch.md +56 -0
  154. graphify/skills/droid/references/exports.md +87 -0
  155. graphify/skills/droid/references/extraction-spec.md +70 -0
  156. graphify/skills/droid/references/github-and-merge.md +46 -0
  157. graphify/skills/droid/references/hooks.md +33 -0
  158. graphify/skills/droid/references/query.md +311 -0
  159. graphify/skills/droid/references/transcribe.md +52 -0
  160. graphify/skills/droid/references/update.md +210 -0
  161. graphify/skills/kilo/references/add-watch.md +56 -0
  162. graphify/skills/kilo/references/exports.md +87 -0
  163. graphify/skills/kilo/references/extraction-spec.md +70 -0
  164. graphify/skills/kilo/references/github-and-merge.md +46 -0
  165. graphify/skills/kilo/references/hooks.md +33 -0
  166. graphify/skills/kilo/references/query.md +311 -0
  167. graphify/skills/kilo/references/transcribe.md +52 -0
  168. graphify/skills/kilo/references/update.md +210 -0
  169. graphify/skills/kiro/references/add-watch.md +56 -0
  170. graphify/skills/kiro/references/exports.md +87 -0
  171. graphify/skills/kiro/references/extraction-spec.md +31 -0
  172. graphify/skills/kiro/references/github-and-merge.md +46 -0
  173. graphify/skills/kiro/references/hooks.md +33 -0
  174. graphify/skills/kiro/references/query.md +311 -0
  175. graphify/skills/kiro/references/transcribe.md +52 -0
  176. graphify/skills/kiro/references/update.md +210 -0
  177. graphify/skills/opencode/references/add-watch.md +56 -0
  178. graphify/skills/opencode/references/exports.md +87 -0
  179. graphify/skills/opencode/references/extraction-spec.md +70 -0
  180. graphify/skills/opencode/references/github-and-merge.md +46 -0
  181. graphify/skills/opencode/references/hooks.md +33 -0
  182. graphify/skills/opencode/references/query.md +311 -0
  183. graphify/skills/opencode/references/transcribe.md +52 -0
  184. graphify/skills/opencode/references/update.md +210 -0
  185. graphify/skills/pi/references/add-watch.md +56 -0
  186. graphify/skills/pi/references/exports.md +87 -0
  187. graphify/skills/pi/references/extraction-spec.md +31 -0
  188. graphify/skills/pi/references/github-and-merge.md +46 -0
  189. graphify/skills/pi/references/hooks.md +33 -0
  190. graphify/skills/pi/references/query.md +311 -0
  191. graphify/skills/pi/references/transcribe.md +52 -0
  192. graphify/skills/pi/references/update.md +210 -0
  193. graphify/skills/trae/references/add-watch.md +56 -0
  194. graphify/skills/trae/references/exports.md +87 -0
  195. graphify/skills/trae/references/extraction-spec.md +70 -0
  196. graphify/skills/trae/references/github-and-merge.md +46 -0
  197. graphify/skills/trae/references/hooks.md +35 -0
  198. graphify/skills/trae/references/query.md +311 -0
  199. graphify/skills/trae/references/transcribe.md +52 -0
  200. graphify/skills/trae/references/update.md +210 -0
  201. graphify/skills/vscode/references/add-watch.md +56 -0
  202. graphify/skills/vscode/references/exports.md +87 -0
  203. graphify/skills/vscode/references/extraction-spec.md +70 -0
  204. graphify/skills/vscode/references/github-and-merge.md +46 -0
  205. graphify/skills/vscode/references/hooks.md +33 -0
  206. graphify/skills/vscode/references/query.md +311 -0
  207. graphify/skills/vscode/references/transcribe.md +52 -0
  208. graphify/skills/vscode/references/update.md +210 -0
  209. graphify/skills/windows/references/add-watch.md +56 -0
  210. graphify/skills/windows/references/exports.md +87 -0
  211. graphify/skills/windows/references/extraction-spec.md +70 -0
  212. graphify/skills/windows/references/github-and-merge.md +46 -0
  213. graphify/skills/windows/references/hooks.md +33 -0
  214. graphify/skills/windows/references/query.md +311 -0
  215. graphify/skills/windows/references/transcribe.md +52 -0
  216. graphify/skills/windows/references/update.md +210 -0
  217. graphify/symbol_resolution.py +556 -0
  218. graphify/transcribe.py +186 -0
  219. graphify/tree_html.py +603 -0
  220. graphify/validate.py +95 -0
  221. graphify/watch.py +2280 -0
  222. graphify/wiki.py +405 -0
  223. graphitect/__init__.py +28 -0
  224. graphitect/__main__.py +4 -0
  225. graphitect/_vendor/__init__.py +2 -0
  226. graphitect/_vendor/archify/LICENSE +22 -0
  227. graphitect/_vendor/archify/SKILL.md +137 -0
  228. graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
  229. graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
  230. graphitect/_vendor/archify/assets/template.html +14935 -0
  231. graphitect/_vendor/archify/bin/archify.mjs +2091 -0
  232. graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
  233. graphitect/_vendor/archify/bin/preview.mjs +653 -0
  234. graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
  235. graphitect/_vendor/archify/brand-marks/README.md +31 -0
  236. graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
  237. graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
  238. graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
  239. graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
  240. graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
  241. graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
  242. graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
  243. graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
  244. graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
  245. graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
  246. graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
  247. graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
  248. graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
  249. graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
  250. graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
  251. graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
  252. graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
  253. graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
  254. graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
  255. graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
  256. graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
  257. graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
  258. graphitect/_vendor/archify/package-lock.json +149 -0
  259. graphitect/_vendor/archify/package.json +39 -0
  260. graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
  261. graphitect/_vendor/archify/references/authoring-contract.md +243 -0
  262. graphitect/_vendor/archify/references/brand-marks.md +65 -0
  263. graphitect/_vendor/archify/references/delivery-contract.md +120 -0
  264. graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
  265. graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
  266. graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
  267. graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
  268. graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  269. graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
  270. graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  271. graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
  272. graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
  273. graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
  274. graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
  275. graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
  276. graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
  277. graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
  278. graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  279. graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
  280. graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
  281. graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
  282. graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
  283. graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
  284. graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
  285. graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
  286. graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
  287. graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
  288. graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
  289. graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
  290. graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
  291. graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
  292. graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  293. graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  294. graphitect/_vendor/archify/schemas/README.md +211 -0
  295. graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
  296. graphitect/_vendor/archify/schemas/common.schema.json +115 -0
  297. graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
  298. graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
  299. graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
  300. graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
  301. graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
  302. graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
  303. graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
  304. graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
  305. graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
  306. graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
  307. graphitect/_vendor/archify/skill-release.json +10 -0
  308. graphitect/cli.py +981 -0
  309. graphitect/deliver/__init__.py +5 -0
  310. graphitect/deliver/archify_adapter.py +1877 -0
  311. graphitect/deliver/archify_ir.py +160 -0
  312. graphitect/deliver/archify_repair.py +135 -0
  313. graphitect/deliver/doc_compiler.py +916 -0
  314. graphitect/ground/__init__.py +5 -0
  315. graphitect/ground/describe_source.py +27 -0
  316. graphitect/ground/fullread_source.py +56 -0
  317. graphitect/ground/graphify_source.py +107 -0
  318. graphitect/models.py +118 -0
  319. graphitect/skill/SKILL.md +80 -0
  320. graphitect/skill/agents/openai.yaml +4 -0
  321. graphitect/synthesize/__init__.py +5 -0
  322. graphitect/synthesize/engine.py +281 -0
  323. graphitect/synthesize/llm_backend.py +331 -0
  324. graphitect/synthesize/questions.py +139 -0
  325. graphitect/synthesize/rubric.py +104 -0
  326. graphitect-0.2.0.dist-info/METADATA +284 -0
  327. graphitect-0.2.0.dist-info/RECORD +336 -0
  328. graphitect-0.2.0.dist-info/WHEEL +5 -0
  329. graphitect-0.2.0.dist-info/entry_points.txt +2 -0
  330. graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
  331. graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
  332. graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
  333. graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
  334. graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
  335. graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
  336. graphitect-0.2.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,160 @@
1
+ """archify's real architecture-mode IR - checked directly against
2
+ archify/references/authoring-contract.md and archify/renderers/architecture/grid.mjs
3
+ (not inferred from its README). See plan.md section 04's "Correction" note.
4
+
5
+ Deliberately narrower than archify's full schema: this covers what
6
+ graphitect.deliver.archify_adapter needs to emit (grid-mode architecture
7
+ diagrams), not every mode/field archify's authoring contract supports.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Literal
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+
17
+ def _drop_empty_lists(value):
18
+ if isinstance(value, dict):
19
+ return {k: _drop_empty_lists(v) for k, v in value.items() if not (isinstance(v, list) and not v)}
20
+ if isinstance(value, list):
21
+ return [_drop_empty_lists(v) for v in value]
22
+ return value
23
+
24
+ ComponentType = Literal[
25
+ "frontend", "backend", "database", "cloud", "security", "messagebus", "external"
26
+ ]
27
+ ConnectionVariant = Literal["default", "emphasis", "security", "dashed"]
28
+ ConnectionSide = Literal["left", "right", "top", "bottom"]
29
+ ConnectionRoute = Literal["auto", "straight", "orthogonal-h", "orthogonal-v"]
30
+
31
+
32
+ class GridLayout(BaseModel):
33
+ mode: Literal["grid"] = "grid"
34
+ cols: int = 4
35
+ # archify's own DEFAULT_GRID (renderers/architecture/grid.mjs) is
36
+ # {cellW: 130, gapX: 30, gapY: 40} - fine for short labels, but confirmed
37
+ # live to produce "label wider than component" and label/connection
38
+ # overlap failures on a real graph with longer labels ("Schema
39
+ # Discoverer Agent") and vertically-adjacent connected components.
40
+ # graphitect widens these based on actual label length instead of
41
+ # leaving archify's short-label default in place - see
42
+ # archify_adapter.to_architecture_ir.
43
+ cell_w: int | None = Field(default=None, alias="cellW")
44
+ cell_h: int | None = Field(default=None, alias="cellH")
45
+ gap_x: int | None = Field(default=None, alias="gapX")
46
+ gap_y: int | None = Field(default=None, alias="gapY")
47
+
48
+ model_config = {"populate_by_name": True}
49
+
50
+
51
+ class Component(BaseModel):
52
+ id: str
53
+ type: ComponentType
54
+ label: str
55
+ sublabel: str | None = None
56
+ row: int | None = None
57
+ col: int | None = None
58
+ pos: tuple[int, int] | None = None # explicit pixel pos wins over row/col
59
+ size: tuple[int, int] | None = None
60
+ tag: str | None = None
61
+ sources: list[dict] = Field(default_factory=list) # repo-evidence links, see authoring-contract.md
62
+
63
+
64
+ class Boundary(BaseModel):
65
+ kind: str # e.g. "region", "security-group"
66
+ label: str
67
+ wraps: list[str] # component ids
68
+
69
+
70
+ class Connection(BaseModel):
71
+ id: str
72
+ from_: str = Field(alias="from")
73
+ to: str
74
+ label: str | None = None
75
+ variant: ConnectionVariant = "default"
76
+ # Routing/label-position overrides - unused by archify_adapter's own
77
+ # deterministic layout (it never sets these), but archify_repair's LLM
78
+ # loop writes them to resolve real validator diagnostics like
79
+ # clean-flow/edge-through-node and layout/constraint (label overlaps
80
+ # component) - see archify/references/authoring-contract.md and
81
+ # archify/schemas/architecture.schema.json's Connection definition,
82
+ # confirmed against the real schema rather than guessed.
83
+ from_side: ConnectionSide | None = Field(default=None, alias="fromSide")
84
+ to_side: ConnectionSide | None = Field(default=None, alias="toSide")
85
+ route: ConnectionRoute | None = None
86
+ via: list[tuple[float, float]] | None = None
87
+ label_at: tuple[float, float] | None = Field(default=None, alias="labelAt")
88
+ label_dx: float | None = Field(default=None, alias="labelDx")
89
+ label_dy: float | None = Field(default=None, alias="labelDy")
90
+ label_segment: int | None = Field(default=None, alias="labelSegment")
91
+
92
+ model_config = {"populate_by_name": True}
93
+
94
+
95
+ class Card(BaseModel):
96
+ dot: str
97
+ title: str
98
+ items: list[str]
99
+
100
+
101
+ class GuidedView(BaseModel):
102
+ """One evidence-grounded chapter in Archify's guided-story rail."""
103
+
104
+ id: str
105
+ label: str
106
+ focus: list[str]
107
+ note: str | None = None
108
+
109
+
110
+ class Meta(BaseModel):
111
+ title: str
112
+ # Architecture's schema supports an explicit [width, height] canvas.
113
+ # Graphitect uses it when a safe detour lane extends past the rightmost
114
+ # component; otherwise Archify's automatic component-only bounds clip
115
+ # the valid connection geometry.
116
+ view_box: tuple[int, int] | None = Field(default=None, alias="viewBox")
117
+ # "draft" was never a real archify value - confirmed against its own
118
+ # --help output (`--quality standard|showcase`) - and defaulting to
119
+ # "showcase" made every real multi-node graph fail archify's strict
120
+ # layout validator (labels wider than boxes, overlapping connections,
121
+ # crossing corridors) since graphitect's own grid layout is a simple
122
+ # heuristic, not hand-tuned for showcase-grade spacing rules. "standard"
123
+ # is the honest default until the layout heuristic itself improves
124
+ # (wider boxes for long labels, real connection routing) rather than
125
+ # just asking archify to be less strict about a still-rough auto-layout.
126
+ quality_profile: Literal["standard", "showcase"] = "standard"
127
+ # Story uses Archify's own focused chapter rail and optional trace
128
+ # motion. Overview and Full rollup stay still until the reader asks.
129
+ animation: Literal["none", "trace"] = "none"
130
+ visual_preset: Literal["classic", "signal-flow", "blueprint", "editorial"] | None = None
131
+ views: list[GuidedView] = Field(default_factory=list)
132
+
133
+ model_config = {"populate_by_name": True}
134
+
135
+
136
+ class ArchitectureIR(BaseModel):
137
+ """A complete document for `archify validate` / `archify deliver`."""
138
+
139
+ schema_version: int = 1
140
+ diagram_type: Literal["architecture"] = "architecture"
141
+ meta: Meta
142
+ layout: GridLayout = Field(default_factory=GridLayout)
143
+ components: list[Component]
144
+ boundaries: list[Boundary] = Field(default_factory=list)
145
+ connections: list[Connection]
146
+ cards: list[Card] = Field(default_factory=list)
147
+
148
+ def model_dump_archify(self) -> dict:
149
+ """Serialize with `from`/`to` un-aliased back to archify's own field
150
+ names, and with empty-list fields dropped entirely rather than
151
+ emitted as `[]`.
152
+
153
+ Confirmed live (12 Sep 2026) against the real `archify validate`:
154
+ archify's schema requires `minItems: 1` on array fields like a
155
+ component's `sources` whenever the key is present at all - pydantic's
156
+ own `default_factory=list` means every Component always carries
157
+ `sources: []` unless explicitly stripped, which fails validation with
158
+ exactly the field populated by default and never actually used.
159
+ """
160
+ return _drop_empty_lists(self.model_dump(by_alias=True, exclude_none=True))
@@ -0,0 +1,135 @@
1
+ """Agent-driven layout repair for archify diagrams.
2
+
3
+ archify_adapter's grid layout is a simple topological-depth heuristic with
4
+ no real connection routing - fine for small graphs, but archify's own
5
+ strict layout validator rejects dense real-world graphs (edges crossing
6
+ unrelated components, labels overlapping boxes - confirmed live against
7
+ FundersAI's real 7,885-node graph). Rather than hand-writing a real graph
8
+ layout/routing algorithm, this asks an LLM to patch the IR's own
9
+ routing/label-position fields (fromSide/toSide/route/via/labelAt/labelDx/
10
+ labelDy/labelSegment) against archify's exact structured diagnostics
11
+ (`archify deliver --json`'s `diagnostics[]`: code, offending connection,
12
+ obstacle, and often a concrete suggested fix), then re-validates - the
13
+ "agent iterates against archify validate feedback" approach flagged as
14
+ future work in plan.md, built as an LLM-backend loop (reusing the same
15
+ Ollama/Anthropic/Gemini backends as Synthesize) so it works from the
16
+ standalone CLI too, not only inside an agent host.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import subprocess
23
+ from pathlib import Path
24
+
25
+ from ..synthesize.llm_backend import LLMBackend
26
+ from .archify_ir import ArchitectureIR
27
+
28
+ _REPAIR_TOOL_NAME = "emit_repaired_architecture_ir"
29
+
30
+
31
+ class RepairUnavailableError(RuntimeError):
32
+ """The optional LLM layout-repair step could not produce a valid IR."""
33
+
34
+ _REPAIR_SYSTEM_PROMPT = """\
35
+ You are repairing an archify architecture diagram IR that failed archify's
36
+ own layout validator. You will be given the current IR (JSON) and the exact
37
+ list of validator diagnostics (each with a code, the offending connection or
38
+ label, the obstacle it collides with, and often a suggested fix).
39
+
40
+ Fix ONLY layout/routing/label-position fields on the existing connections:
41
+ fromSide, toSide, route ("auto"|"straight"|"orthogonal-h"|"orthogonal-v"),
42
+ via (a list of [x, y] waypoints), labelAt ([x, y]), labelDx, labelDy,
43
+ labelSegment. You may also adjust a component's row/col/pos/size if a
44
+ diagnostic specifically calls for moving it. Do NOT change any id, label,
45
+ from/to endpoint, or add/remove any component or connection - every
46
+ diagnostic must be resolved by adjusting geometry, not by altering what the
47
+ diagram says. When a diagnostic's message includes a "Suggested fix" with
48
+ concrete coordinates, prefer using those exact numbers over estimating your
49
+ own.
50
+
51
+ Return the complete corrected IR as a single JSON object matching the given
52
+ schema exactly - every field from the input IR must still be present unless
53
+ you are deliberately changing it.
54
+ """
55
+
56
+
57
+ def run_deliver_json(
58
+ bin_cmd: list[str], ir_path: Path, out_path: Path, *, diagram_type: str = "architecture"
59
+ ) -> dict:
60
+ """Runs `archify deliver ... --json` and returns its parsed JSON report
61
+ regardless of exit code. Confirmed live: archify's own --json output
62
+ carries structured `diagnostics` (code/subject/evidence/supportedFixes)
63
+ on failure - the identical shape `archify validate --json` gives - so a
64
+ single deliver attempt is enough to both try rendering and get repair
65
+ feedback if it fails, with no separate validate call needed.
66
+
67
+ Not underscore-prefixed: also used by archify_adapter.render()'s
68
+ no-LLM-backend mechanical label-fix retry (_apply_suggested_label_fixes)
69
+ - both need the exact same structured diagnostics, LLM-driven repair or
70
+ not.
71
+ """
72
+ result = subprocess.run(
73
+ [*bin_cmd, "deliver", diagram_type, str(ir_path), str(out_path), "--json"],
74
+ capture_output=True,
75
+ text=True,
76
+ encoding="utf-8",
77
+ errors="replace",
78
+ check=False,
79
+ )
80
+ try:
81
+ return json.loads(result.stdout)
82
+ except json.JSONDecodeError as exc:
83
+ raise RuntimeError(
84
+ f"archify deliver --json produced non-JSON output (exit {result.returncode}): "
85
+ f"stdout={result.stdout[:500]!r} stderr={result.stderr[:500]!r}"
86
+ ) from exc
87
+
88
+
89
+ def repair_and_deliver(
90
+ ir: ArchitectureIR,
91
+ bin_cmd: list[str],
92
+ ir_path: Path,
93
+ out_path: Path,
94
+ backend: LLMBackend,
95
+ *,
96
+ max_iterations: int = 3,
97
+ ) -> dict:
98
+ """Writes `ir`, attempts `archify deliver`, and if archify's validator
99
+ rejects it, asks `backend` to patch the IR against the real diagnostics
100
+ and retries - up to `max_iterations` total delivery attempts. Returns
101
+ the final JSON report (whatever the last attempt produced, success or
102
+ failure) - the caller decides what to do with a still-failing report.
103
+ """
104
+ if max_iterations < 1:
105
+ raise ValueError("max_iterations must be >= 1")
106
+
107
+ schema = ArchitectureIR.model_json_schema()
108
+
109
+ for attempt in range(1, max_iterations + 1):
110
+ ir_path.write_text(json.dumps(ir.model_dump_archify(), indent=2), encoding="utf-8")
111
+ report = run_deliver_json(bin_cmd, ir_path, out_path)
112
+ if report.get("ok"):
113
+ return report
114
+
115
+ diagnostics = report.get("diagnostics") or []
116
+ if not diagnostics or attempt == max_iterations:
117
+ return report
118
+
119
+ user_prompt = (
120
+ f"Current IR:\n{json.dumps(ir.model_dump_archify(), indent=2)}\n\n"
121
+ f"Validator diagnostics ({len(diagnostics)} issue(s)):\n"
122
+ f"{json.dumps(diagnostics, indent=2)}"
123
+ )
124
+ try:
125
+ patched = backend.complete_json(
126
+ _REPAIR_SYSTEM_PROMPT, user_prompt, schema, tool_name=_REPAIR_TOOL_NAME
127
+ )
128
+ ir = ArchitectureIR.model_validate(patched)
129
+ except Exception as exc:
130
+ # Layout repair is opportunistic. A provider quota/outage or an
131
+ # invalid structured response must leave render() free to use
132
+ # its deterministic, no-LLM fallback instead of crashing build.
133
+ raise RepairUnavailableError("optional LLM layout repair is unavailable") from exc
134
+
135
+ raise AssertionError("unreachable - loop always returns") # pragma: no cover