Skip to content

FE-1322: Generate Petrinaut architecture docs from in-code annotations - #9204

Merged
kube merged 1 commit into
mainfrom
cf/fe-1322-arch-docs-generator
Aug 13, 2026
Merged

FE-1322: Generate Petrinaut architecture docs from in-code annotations#9204
kube merged 1 commit into
mainfrom
cf/fe-1322-arch-docs-generator

Conversation

@kube

@kube kube commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Generates the Petrinaut architecture docs from annotations in the source, and fails the build when an annotation stops matching the code.

This replaces petrinaut-core/scripts/generate-dependency-diagrams.mjs. That script held the layer mapping as ~180 lines of if (path.startsWith(...)), and put anything it did not match into a default bucket without reporting it. It also hard-coded 7 of petrinaut-core's 10 entry points, so imports through ./ai, ./optimization and ./compiled-model never appeared in the diagrams.

The output is a bundle of files, not a website, so it can be rendered locally, embedded in hash.dev, or read as plain text.

First of three PRs. #9205 moves the hand-written architecture prose into the bundle. #9206 adds a site that renders it.

flowchart LR
  A["@layerRoot + @role"] --> E[Extractor]
  B["README frontmatter"] --> E
  E --> M[Model]
  C["Import graph"] --> M
  M --> O["bundle/"]
Loading

🔍 What does this change?

Declaring a layer

Two tags on a folder's main file:

/**
 * @layerRoot core.simulation.monte-carlo
 * @role Runs many simulations with bounded frame memory
 */

A folder README.md can declare the same two things in frontmatter, and its prose becomes that layer's page.

Files with no annotation belong to the nearest ancestor folder that declares one. 37 declarations cover 413 files. The layer sizes, the 178 edges between layers, and the parent/child tree are read from the TypeScript import graph using dependency-cruiser.

There are only two tags. Both are needed to put a layer in the graph and label it. A third would state something the generator cannot check against the code.

Output

Written to bundle/, which is git-ignored:

File Contents
architecture.json Layers, edges, configured rules
architecture.md The whole architecture in one file
manifest.json Page tree, for building navigation
pages/**.mdx One page per layer
diagrams/**.{d2,svg} Diagram sources and renders

Generated MDX is YAML frontmatter plus CommonMark, with no JSX and no imports.

Diagrams

The build generates 44 D2 files from the model and renders them to SVG. A box exists because a layer does, an arrow because an import does:

  • 1 overview. The top-level layers.
  • 37 neighbourhoods. One per layer, showing what it imports and what imports it.
  • 6 drill-downs. The direct children of each layer that has any.

Arrow labels are sums of file-level imports. A neighbourhood draws at most 12 neighbours: core.types has 18, so 12 are drawn and the other 6 become one node carrying their combined count.

What fails the build

doc:architecture refuses to write, and lint:arch-docs exits 1, on:

  • a source file that no declaration covers
  • a source file the import graph reached that no layer claims
  • a layer id that implies an ancestor nothing declares
  • a duplicate layer id, or two declarations on one folder
  • a malformed tag, or an unknown key in a declaring README
  • a package configured for a language with no extractor
  • an exports subpath with no source file behind it
  • a rule that names a layer which does not exist
  • a dependency that violates a rule in architecture.config.ts

Four rules are configured. The main one is that react must not import ui. It holds today: 0 imports, against 235 in the other direction.

Four of these checks exist because the failure would otherwise remove coverage instead of reporting an error. An exports subpath that stops resolving, or a rule with a typo, leaves a build that passes while checking less than before.

🔗 Related links

  • FE-1322
  • FE-1157, moving Petrinaut docs to hash.dev/docs/petrinaut. This PR produces the files that work needs. It publishes nothing.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

@hashintel/petrinaut and petrinaut-core change only in comments, READMEs, and the removal of a private script with its dependency-cruiser devDependency. No runtime code, types or exports change.

📜 Does this require a change to the docs?

  • require changes to docs which are made as part of this PR

The user-facing guide (libs/@hashintel/petrinaut/docs/) is untouched, since no UI or behaviour changed. AGENTS.md gains a section on declaring layers and what the build enforces.

🕸️ Does this require a change to the Turbo Graph?

  • affected the execution graph, and the turbo.json's have been updated to reflect this

Adds @local/petrinaut-arch-docs#doc:architecture. It is uncached: Turborepo hashes a package plus its dependencies' task outputs, and this task reads source comments in petrinaut and petrinaut-core, which are nobody's output. A cached bundle would survive an annotation change and stop matching the code. The task declares outputs, so consumers depend on the task rather than on the directory existing.

Removes doc:dependency-diagram from petrinaut-core.

⚠️ Known issues

  • Every layer has one parent, so a shared foundation has no natural place. core.types is the example: four separate parents across two packages depend on it, so it sits under core while being used everywhere.
  • @role is not verified. The structure is checked against the import graph. The one-line description is prose, and nothing confirms it is accurate.
  • Layer names come from the last segment of the id, so ui renders as "Ui". Change the id if a name reads badly. A separate display-name tag was considered and left out.
  • Three packages are not covered: petrinaut-cli, petrinaut-website, petrinaut-opt. A TypeScript package needs a config entry and one root declaration. The Python app needs an extractor that does not exist yet, and configuring it without one is an error rather than a silent skip.
  • Nothing runs these checks in CI on this PR. They run wherever the bundle is built, and nothing here builds it. The first consumer is FE-1322: Add a Starlight site that renders the docs bundle #9206, whose lint:tsc and build both depend on doc:architecture. Until then, run lint:arch-docs locally. It takes about 2.5 seconds and does not need d2.
  • mise run fix:package-json needs a nightly Cargo feature and could not run locally, so package.json key ordering was checked by hand against the sorter's field list.

🐾 Next steps

🛡 What tests cover this?

75 tests across 7 files in @local/petrinaut-arch-docs:

  • tags.test.ts: tag grammar, wrapped values, duplicates, miscased tags, tags named in prose.
  • frontmatter.test.ts: declarations, malformed YAML, half-written declarations, CRLF, unknown keys, and the YAML cases that a line-based parser got wrong.
  • extract.test.ts: inheritance through folders that declare nothing, uncovered files, ordering.
  • check.test.ts: each check in both directions, so it fires when broken and stays quiet when not.
  • scope.test.ts: source roots, path escaping, and the exclusion pattern the extractor and the graph share.
  • emit/d2.test.ts: neighbourhood diagrams, edge directions, and the 12-neighbour cap.
  • emit/mdx.test.ts: link resolution at different depths, fragments, unresolved targets.

Existing suites are unaffected: 842 (petrinaut-core), 187 (petrinaut).

❓ How to test this?

yarn workspace @local/petrinaut-arch-docs lint:arch-docs   # 0 errors, ~2.5s
turbo run doc:architecture --filter @local/petrinaut-arch-docs

Read bundle/architecture.md for the whole model, and open bundle/diagrams/around/core.types.svg, which is the diagram that hits the 12-neighbour cap.

To see the checks fire: empty a role: in a declaring README, add a source file in a folder no declaration covers, or add a rule to architecture.config.ts naming a layer that does not exist. Each fails lint:arch-docs and names the file.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview Aug 12, 2026 7:17pm
petrinaut Error Error Aug 12, 2026 7:17pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Aug 12, 2026 7:17pm

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team type/eng > backend Owned by the @backend team type/legal Owned by the @legal team labels Aug 12, 2026
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

The following issues were found:
  • ❌ 1 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

Vulnerabilities

libs/@local/petrinaut-arch-docs/package.json

NameVersionVulnerabilitySeverityPatched Version
js-yaml4.3.0JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026-59870 fix not backportedhigh4.3.1

License Issues

yarn.lock

PackageVersionLicenseIssue Type
@local/petrinaut-arch-docs@workspace:libs/0.0.0-use.localNullUnknown License

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
npm/js-yaml 4.3.0 🟢 6.1
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 1Found 3/30 approved changesets -- score normalized to 1
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 1030 commit(s) and 27 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions🟢 10GitHub workflow tokens follow principle of least privilege
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
License🟢 10license file detected
Fuzzing🟢 10project is fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 4security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/@hashintel/petrinaut workspace:* UnknownUnknown
npm/@hashintel/petrinaut-core workspace:* UnknownUnknown
npm/@local/tsconfig workspace:* UnknownUnknown
npm/@types/js-yaml ^4 UnknownUnknown
npm/@types/node 22.18.13 🟢 6.6
Details
CheckScoreReason
Code-Review🟢 9Found 29/30 approved changesets -- score normalized to 9
Maintained🟢 1030 commit(s) and 4 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy🟢 10security policy file detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 9license file detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Signed-Releases⚠️ -1no releases found
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies🟢 8dependency not pinned by hash detected -- score normalized to 8
Fuzzing⚠️ 0project is not fuzzed
npm/dependency-cruiser 18.0.0 🟢 7
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 9 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review⚠️ 0Found 0/30 approved changesets -- score normalized to 0
Security-Policy🟢 10security policy file detected
Token-Permissions🟢 8detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Signed-Releases⚠️ -1no releases found
SAST🟢 10SAST tool is run on all commits
Packaging🟢 10packaging workflow detected
npm/oxlint 1.63.0 UnknownUnknown
npm/tsx 4.20.6 UnknownUnknown
npm/typescript 5.9.3 🟢 7.9
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Maintained🟢 1016 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
Dependency-Update-Tool🟢 10update tool detected
Security-Policy🟢 10security policy file detected
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Vulnerabilities⚠️ 012 existing vulnerabilities detected
Binary-Artifacts🟢 10no binaries found in the repo
License🟢 10license file detected
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Pinned-Dependencies🟢 7dependency not pinned by hash detected -- score normalized to 7
SAST🟢 10SAST tool is run on all commits
Branch-Protection⚠️ -1internal error: error during GetBranch(release-5.9): error during branchesHandler.query: internal error: githubv4.Query: Resource not accessible by integration
Fuzzing🟢 10project is fuzzed
CI-Tests🟢 1030 out of 30 merged PRs checked by a CI test -- score normalized to 10
Contributors🟢 10project has 38 contributing companies or organizations
npm/vitest 4.1.10 UnknownUnknown
npm/zod 4.4.3 🟢 5.1
Details
CheckScoreReason
Code-Review⚠️ 1Found 5/29 approved changesets -- score normalized to 1
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Security-Policy🟢 10security policy file detected
Maintained🟢 1013 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Signed-Releases⚠️ -1no releases found
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/@local/petrinaut-arch-docs@workspace:libs/ 0.0.0-use.local UnknownUnknown

Scanned Files

  • libs/@hashintel/petrinaut-core/package.json
  • libs/@local/petrinaut-arch-docs/package.json
  • yarn.lock

Comment thread libs/@local/petrinaut-arch-docs/src/emit/mdx.ts Fixed
Comment thread libs/@local/petrinaut-arch-docs/src/frontmatter.ts
Comment thread libs/@local/petrinaut-arch-docs/src/frontmatter.ts Fixed
Comment thread libs/@local/petrinaut-arch-docs/src/frontmatter.ts Fixed
Comment thread libs/@local/petrinaut-arch-docs/src/content.ts Fixed
@codspeed-hq

codspeed-hq Bot commented Aug 12, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 98 untouched benchmarks


Comparing cf/fe-1322-arch-docs-generator (69bc7b5) with main (3c6dc3d)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (866383e) during the generation of this report, so 3c6dc3d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark results

@rust/hash-graph-benches – Integrations

policy_resolution_large

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2002 $$28.1 \mathrm{ms} \pm 176 \mathrm{μs}\left({\color{gray}0.164 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.54 \mathrm{ms} \pm 22.0 \mathrm{μs}\left({\color{gray}-2.010 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 1002 $$13.7 \mathrm{ms} \pm 113 \mathrm{μs}\left({\color{gray}-1.824 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 3314 $$44.7 \mathrm{ms} \pm 405 \mathrm{μs}\left({\color{gray}-1.059 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$15.0 \mathrm{ms} \pm 136 \mathrm{μs}\left({\color{gray}-4.387 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 1527 $$25.2 \mathrm{ms} \pm 175 \mathrm{μs}\left({\color{gray}-1.958 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 2078 $$29.2 \mathrm{ms} \pm 194 \mathrm{μs}\left({\color{gray}-0.844 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.88 \mathrm{ms} \pm 29.2 \mathrm{μs}\left({\color{gray}-1.397 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 1033 $$15.1 \mathrm{ms} \pm 119 \mathrm{μs}\left({\color{gray}-0.614 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_medium

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 102 $$3.89 \mathrm{ms} \pm 22.6 \mathrm{μs}\left({\color{gray}-0.493 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$3.09 \mathrm{ms} \pm 19.9 \mathrm{μs}\left({\color{gray}-0.328 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 52 $$3.49 \mathrm{ms} \pm 24.4 \mathrm{μs}\left({\color{gray}0.884 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 269 $$5.33 \mathrm{ms} \pm 47.1 \mathrm{μs}\left({\color{gray}-1.260 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.64 \mathrm{ms} \pm 21.3 \mathrm{μs}\left({\color{gray}-0.278 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 108 $$4.28 \mathrm{ms} \pm 27.9 \mathrm{μs}\left({\color{gray}-1.124 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 133 $$4.59 \mathrm{ms} \pm 37.1 \mathrm{μs}\left({\color{gray}0.227 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.58 \mathrm{ms} \pm 24.7 \mathrm{μs}\left({\color{gray}-0.618 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 63 $$4.24 \mathrm{ms} \pm 35.4 \mathrm{μs}\left({\color{gray}-0.773 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_none

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 2 $$2.76 \mathrm{ms} \pm 16.9 \mathrm{μs}\left({\color{gray}-2.082 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.57 \mathrm{ms} \pm 13.3 \mathrm{μs}\left({\color{gray}-2.186 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 2 $$2.76 \mathrm{ms} \pm 18.5 \mathrm{μs}\left({\color{gray}-0.634 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 8 $$3.02 \mathrm{ms} \pm 24.7 \mathrm{μs}\left({\color{gray}-1.164 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$2.80 \mathrm{ms} \pm 17.8 \mathrm{μs}\left({\color{gray}-1.427 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 3 $$2.99 \mathrm{ms} \pm 17.3 \mathrm{μs}\left({\color{gray}-1.682 \mathrm{\%}}\right) $$ Flame Graph

policy_resolution_small

Function Value Mean Flame graphs
resolve_policies_for_actor user: empty, selectivity: high, policies: 52 $$3.14 \mathrm{ms} \pm 22.8 \mathrm{μs}\left({\color{gray}-2.846 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: low, policies: 1 $$2.86 \mathrm{ms} \pm 20.9 \mathrm{μs}\left({\color{gray}-2.865 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: empty, selectivity: medium, policies: 26 $$3.06 \mathrm{ms} \pm 24.8 \mathrm{μs}\left({\color{gray}-3.247 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: high, policies: 94 $$3.58 \mathrm{ms} \pm 24.4 \mathrm{μs}\left({\color{gray}0.023 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: low, policies: 1 $$3.09 \mathrm{ms} \pm 21.1 \mathrm{μs}\left({\color{gray}0.264 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: seeded, selectivity: medium, policies: 27 $$3.43 \mathrm{ms} \pm 25.5 \mathrm{μs}\left({\color{gray}0.121 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: high, policies: 66 $$3.50 \mathrm{ms} \pm 27.6 \mathrm{μs}\left({\color{gray}-1.877 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: low, policies: 1 $$3.03 \mathrm{ms} \pm 16.7 \mathrm{μs}\left({\color{gray}-3.891 \mathrm{\%}}\right) $$ Flame Graph
resolve_policies_for_actor user: system, selectivity: medium, policies: 29 $$3.40 \mathrm{ms} \pm 23.5 \mathrm{μs}\left({\color{gray}-4.049 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_complete

Function Value Mean Flame graphs
entity_by_id;one_depth 1 entities $$44.9 \mathrm{ms} \pm 235 \mathrm{μs}\left({\color{gray}-0.251 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 10 entities $$35.1 \mathrm{ms} \pm 213 \mathrm{μs}\left({\color{gray}-0.860 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 25 entities $$38.7 \mathrm{ms} \pm 301 \mathrm{μs}\left({\color{gray}2.08 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 5 entities $$33.9 \mathrm{ms} \pm 245 \mathrm{μs}\left({\color{gray}0.039 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;one_depth 50 entities $$44.3 \mathrm{ms} \pm 295 \mathrm{μs}\left({\color{gray}-1.560 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 1 entities $$52.4 \mathrm{ms} \pm 337 \mathrm{μs}\left({\color{gray}0.466 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 10 entities $$42.5 \mathrm{ms} \pm 278 \mathrm{μs}\left({\color{gray}-0.701 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 25 entities $$95.3 \mathrm{ms} \pm 672 \mathrm{μs}\left({\color{gray}-0.682 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 5 entities $$36.3 \mathrm{ms} \pm 214 \mathrm{μs}\left({\color{gray}-0.214 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;two_depth 50 entities $$284 \mathrm{ms} \pm 897 \mathrm{μs}\left({\color{gray}-0.148 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 1 entities $$11.7 \mathrm{ms} \pm 75.5 \mathrm{μs}\left({\color{gray}-2.368 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 10 entities $$11.8 \mathrm{ms} \pm 59.4 \mathrm{μs}\left({\color{gray}-1.674 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 25 entities $$11.8 \mathrm{ms} \pm 71.7 \mathrm{μs}\left({\color{gray}-1.237 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 5 entities $$11.7 \mathrm{ms} \pm 76.6 \mathrm{μs}\left({\color{gray}-2.626 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id;zero_depth 50 entities $$11.7 \mathrm{ms} \pm 76.2 \mathrm{μs}\left({\color{gray}-1.800 \mathrm{\%}}\right) $$ Flame Graph

read_scaling_linkless

Function Value Mean Flame graphs
entity_by_id 1 entities $$11.6 \mathrm{ms} \pm 78.0 \mathrm{μs}\left({\color{gray}-1.938 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$11.8 \mathrm{ms} \pm 65.2 \mathrm{μs}\left({\color{gray}-1.544 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$11.9 \mathrm{ms} \pm 80.6 \mathrm{μs}\left({\color{gray}-0.131 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$11.8 \mathrm{ms} \pm 84.3 \mathrm{μs}\left({\color{gray}-3.046 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10000 entities $$12.0 \mathrm{ms} \pm 69.0 \mathrm{μs}\left({\color{gray}-1.714 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$12.2 \mathrm{ms} \pm 83.4 \mathrm{μs}\left({\color{gray}0.328 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$12.4 \mathrm{ms} \pm 90.5 \mathrm{μs}\left({\color{gray}-0.174 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$12.1 \mathrm{ms} \pm 71.5 \mathrm{μs}\left({\color{gray}-3.791 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$12.4 \mathrm{ms} \pm 76.7 \mathrm{μs}\left({\color{gray}1.44 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$12.2 \mathrm{ms} \pm 73.7 \mathrm{μs}\left({\color{gray}-1.092 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$12.4 \mathrm{ms} \pm 80.7 \mathrm{μs}\left({\color{gray}0.863 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$12.2 \mathrm{ms} \pm 71.7 \mathrm{μs}\left({\color{gray}-0.748 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$12.2 \mathrm{ms} \pm 74.0 \mathrm{μs}\left({\color{gray}-0.217 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$12.4 \mathrm{ms} \pm 72.6 \mathrm{μs}\left({\color{gray}0.327 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba $$9.11 \mathrm{ms} \pm 74.8 \mathrm{μs}\left({\color{gray}0.552 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property traversal_paths=0 0 $$61.4 \mathrm{ms} \pm 417 \mathrm{μs}\left({\color{gray}4.30 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$118 \mathrm{ms} \pm 771 \mathrm{μs}\left({\color{gray}4.57 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$69.7 \mathrm{ms} \pm 559 \mathrm{μs}\left({\color{gray}3.63 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$79.5 \mathrm{ms} \pm 501 \mathrm{μs}\left({\color{gray}3.66 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$89.4 \mathrm{ms} \pm 498 \mathrm{μs}\left({\color{gray}4.53 \mathrm{\%}}\right) $$
entity_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$95.4 \mathrm{ms} \pm 716 \mathrm{μs}\left({\color{gray}4.81 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=0 0 $$46.5 \mathrm{ms} \pm 287 \mathrm{μs}\left({\color{gray}3.12 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=255 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true $$74.7 \mathrm{ms} \pm 492 \mathrm{μs}\left({\color{gray}-0.060 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false $$52.1 \mathrm{ms} \pm 391 \mathrm{μs}\left({\color{gray}0.567 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true $$61.2 \mathrm{ms} \pm 349 \mathrm{μs}\left({\color{gray}0.664 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true $$64.0 \mathrm{ms} \pm 396 \mathrm{μs}\left({\color{gray}0.258 \mathrm{\%}}\right) $$
link_by_source_by_property traversal_paths=2 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true $$63.5 \mathrm{ms} \pm 482 \mathrm{μs}\left({\color{gray}-0.050 \mathrm{\%}}\right) $$

scenarios

Function Value Mean Flame graphs
full_test query-limited $$123 \mathrm{ms} \pm 843 \mathrm{μs}\left({\color{gray}2.52 \mathrm{\%}}\right) $$ Flame Graph
full_test query-unlimited $$134 \mathrm{ms} \pm 778 \mathrm{μs}\left({\color{gray}2.36 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-limited $$20.5 \mathrm{ms} \pm 170 \mathrm{μs}\left({\color{gray}2.18 \mathrm{\%}}\right) $$ Flame Graph
linked_queries query-unlimited $$546 \mathrm{ms} \pm 1.38 \mathrm{ms}\left({\color{gray}0.884 \mathrm{\%}}\right) $$ Flame Graph

@kube
kube marked this pull request as ready for review August 12, 2026 17:51
@kube
kube requested a review from a team as a code owner August 12, 2026 17:51
Copilot AI balanced review requested due to automatic review settings August 12, 2026 17:51
@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

PR Summary

Cursor Bugbot is generating a summary for commit bb21633. Configure here.

Comment thread libs/@local/petrinaut-arch-docs/src/extract.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an annotation-driven Petrinaut architecture documentation generator, replacing the manually maintained dependency diagrams.

Changes:

  • Extracts layers from source annotations and README frontmatter, validating them against imports and architecture rules.
  • Generates Markdown/MDX, JSON manifests, and D2/SVG diagrams with comprehensive tests.
  • Annotates Petrinaut packages and removes the legacy diagram generator.

Reviewed changes

Copilot reviewed 79 out of 82 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
yarn.lock Moves dependency-cruiser to the new package.
libs/@local/petrinaut-arch-docs/turbo.json Defines documentation tasks and outputs.
libs/@local/petrinaut-arch-docs/tsconfig.json Configures TypeScript compilation.
libs/@local/petrinaut-arch-docs/src/tags.ts Scans source annotations.
libs/@local/petrinaut-arch-docs/src/tags.test.ts Tests annotation parsing.
libs/@local/petrinaut-arch-docs/src/scope.ts Defines source inclusion rules.
libs/@local/petrinaut-arch-docs/src/scope.test.ts Tests source scope patterns.
libs/@local/petrinaut-arch-docs/src/paths.ts Normalizes repository paths.
libs/@local/petrinaut-arch-docs/src/model.ts Defines the architecture schema.
libs/@local/petrinaut-arch-docs/src/index.ts Exports the bundle manifest type.
libs/@local/petrinaut-arch-docs/src/graph.ts Builds layer dependency edges.
libs/@local/petrinaut-arch-docs/src/frontmatter.ts Parses README declarations.
libs/@local/petrinaut-arch-docs/src/frontmatter.test.ts Tests declaration frontmatter.
libs/@local/petrinaut-arch-docs/src/extract.ts Assigns source files to layers.
libs/@local/petrinaut-arch-docs/src/extract.test.ts Tests layer extraction.
libs/@local/petrinaut-arch-docs/src/emit/mdx.ts Generates architecture pages.
libs/@local/petrinaut-arch-docs/src/emit/mdx.test.ts Tests generated link resolution.
libs/@local/petrinaut-arch-docs/src/emit/d2.ts Generates and renders diagrams.
libs/@local/petrinaut-arch-docs/src/emit/d2.test.ts Tests neighborhood diagrams.
libs/@local/petrinaut-arch-docs/src/emit/bundle-outputs.ts Generates bundle indexes.
libs/@local/petrinaut-arch-docs/src/diagnostics.ts Defines diagnostics.
libs/@local/petrinaut-arch-docs/src/content.ts Collects authored documentation.
libs/@local/petrinaut-arch-docs/src/cli.ts Implements build and check commands.
libs/@local/petrinaut-arch-docs/src/check.ts Enforces architecture invariants.
libs/@local/petrinaut-arch-docs/src/check.test.ts Tests architecture checks.
libs/@local/petrinaut-arch-docs/src/build.ts Orchestrates bundle generation.
libs/@local/petrinaut-arch-docs/README.md Documents the generator.
libs/@local/petrinaut-arch-docs/package.json Declares package scripts and dependencies.
libs/@local/petrinaut-arch-docs/LICENSE.md Adds license summary.
libs/@local/petrinaut-arch-docs/LICENSE-MIT.md Adds MIT license.
libs/@local/petrinaut-arch-docs/LICENSE-APACHE.md Adds Apache license.
libs/@local/petrinaut-arch-docs/dependency-cruiser.tsconfig.json Configures import resolution.
libs/@local/petrinaut-arch-docs/architecture.config.ts Configures packages and rules.
libs/@local/petrinaut-arch-docs/.oxlintrc.json Configures linting.
libs/@local/petrinaut-arch-docs/.gitignore Ignores generated bundles.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/sdcpn-view.tsx Declares the canvas layer.
libs/@hashintel/petrinaut/src/ui/views/README.md Declares the views layer.
libs/@hashintel/petrinaut/src/ui/views/Editor/editor-view.tsx Declares the editor layer.
libs/@hashintel/petrinaut/src/ui/monaco/provider.tsx Declares the Monaco layer.
libs/@hashintel/petrinaut/src/ui/index.ts Declares the UI root layer.
libs/@hashintel/petrinaut/src/react/state/README.md Declares the React state layer.
libs/@hashintel/petrinaut/src/react/simulation/provider.tsx Declares the simulation provider layer.
libs/@hashintel/petrinaut/src/react/playback/README.md Declares the playback layer.
libs/@hashintel/petrinaut/src/react/lsp/provider.tsx Declares the React LSP layer.
libs/@hashintel/petrinaut/src/react/index.ts Declares the React root layer.
libs/@hashintel/petrinaut/src/react/hooks/index.ts Declares the hooks layer.
libs/@hashintel/petrinaut/src/react/experiments/provider.tsx Declares the experiments layer.
libs/@hashintel/petrinaut/src/react/execution-frame/provider.tsx Declares the execution-frame layer.
libs/@hashintel/petrinaut/src/main.ts Declares the host-facing layer.
libs/@hashintel/petrinaut/ARCHITECTURE.md Updates architecture documentation links.
libs/@hashintel/petrinaut-core/src/workers/README.md Declares the worker-entry layer.
libs/@hashintel/petrinaut-core/src/validation/README.md Declares the validation layer.
libs/@hashintel/petrinaut-core/src/types/sdcpn.ts Declares the types layer.
libs/@hashintel/petrinaut-core/src/store/index.ts Declares the store layer.
libs/@hashintel/petrinaut-core/src/simulation/worker/README.md Declares the simulation worker layer.
libs/@hashintel/petrinaut-core/src/simulation/runtime/simulation.ts Declares the runtime layer.
libs/@hashintel/petrinaut-core/src/simulation/README.md Declares and documents simulation.
libs/@hashintel/petrinaut-core/src/simulation/monte-carlo/README.md Declares the Monte Carlo layer.
libs/@hashintel/petrinaut-core/src/simulation/frames/frame-reader.ts Declares the frames layer.
libs/@hashintel/petrinaut-core/src/simulation/engine/README.md Declares the engine layer.
libs/@hashintel/petrinaut-core/src/simulation/authoring/sandbox.ts Declares the authoring layer.
libs/@hashintel/petrinaut-core/src/simulation/ARCHITECTURE.md Updates deep-dive documentation links.
libs/@hashintel/petrinaut-core/src/schemas/entity-schemas.ts Declares the schemas layer.
libs/@hashintel/petrinaut-core/src/playback/index.ts Declares core playback.
libs/@hashintel/petrinaut-core/src/lsp/worker/language-server.worker.ts Declares the LSP worker layer.
libs/@hashintel/petrinaut-core/src/lsp/index.ts Declares the LSP layer.
libs/@hashintel/petrinaut-core/src/layout/index.ts Declares the layout layer.
libs/@hashintel/petrinaut-core/src/index.ts Declares the core root layer.
libs/@hashintel/petrinaut-core/src/hir/README.md Declares the HIR layer.
libs/@hashintel/petrinaut-core/src/handle/index.ts Declares the handle layer.
libs/@hashintel/petrinaut-core/src/file-format/parse-sdcpn-file.ts Declares the file-format layer.
libs/@hashintel/petrinaut-core/src/examples/index.ts Declares the examples layer.
libs/@hashintel/petrinaut-core/src/clipboard/paste.ts Declares the clipboard layer.
libs/@hashintel/petrinaut-core/src/actual-mode/README.md Declares the actual-mode layer.
libs/@hashintel/petrinaut-core/scripts/generate-dependency-diagrams.mjs Removes the legacy generator.
libs/@hashintel/petrinaut-core/package.json Removes the legacy script and dependency.
libs/@hashintel/petrinaut-core/docs/architecture/petrinaut-dependencies.d2 Removes an obsolete generated diagram.
libs/@hashintel/petrinaut-core/docs/architecture/petrinaut-compilation-dependencies.d2 Removes an obsolete compilation diagram.
libs/@hashintel/petrinaut-core/docs/architecture/dependency-diagrams.md Removes obsolete diagram instructions.
AGENTS.md Documents architecture annotation requirements.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libs/@local/petrinaut-arch-docs/src/build.ts Outdated
Comment thread libs/@local/petrinaut-arch-docs/src/cli.ts
Comment thread libs/@local/petrinaut-arch-docs/src/build.ts Outdated
Comment thread libs/@local/petrinaut-arch-docs/src/content.ts
Comment thread libs/@local/petrinaut-arch-docs/src/emit/mdx.ts Outdated
Comment thread libs/@local/petrinaut-arch-docs/README.md Outdated
Comment thread libs/@local/petrinaut-arch-docs/src/tags.ts
@kube
kube force-pushed the cf/fe-1322-arch-docs-generator branch from bb21633 to f056856 Compare August 12, 2026 18:15
Copilot AI review requested due to automatic review settings August 12, 2026 18:15

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f056856. Configure here.

Comment thread libs/@local/petrinaut-arch-docs/src/content.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 79 out of 82 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

libs/@local/petrinaut-arch-docs/src/extract.ts:261

  • A known @role without @layerRoot is currently ignored. In a folder already covered by an ancestor, a half-written child declaration therefore passes all checks and silently leaves those files in the parent layer—the same coverage-loss case that parseFrontmatter explicitly rejects for README declarations. Report the orphaned role before handling a complete declaration.
    libs/@local/petrinaut-arch-docs/src/extract.ts:230
  • This parses every Markdown file as a layer declaration, although the declared contract limits folder declarations to README.md. For example, adding ordinary frontmatter with layer/role to BUFFER_ABI.md would unexpectedly claim its whole folder (or conflict with the real README) instead of remaining a reference. Restrict declaration parsing to files named README.md; pass 2 can continue collecting other Markdown files as references.

Comment thread libs/@local/petrinaut-arch-docs/src/content.ts Outdated
The architecture was described in a script that sat nowhere near the code it
described: ~180 lines of `if (path.startsWith(...))` in
`generate-dependency-diagrams.mjs`, with a fallback that silently mis-bucketed
anything renamed. It also hard-coded 7 of petrinaut-core's 10 entry points, so
imports through `./ai`, `./optimization` and `./compiled-model` were absent
from the diagrams entirely.

This replaces it with declarations that live beside the code, and a generator
that joins them with the real import graph.

A declaration is two tags. `@layerRoot <id>` names the layer a folder and its
descendants form; `@role <one line>` says what it is for. A folder README's
frontmatter declares the same pair, and its prose becomes that layer's page.
Files with no annotation inherit from the nearest declaring ancestor, which is
what keeps this proportional to the architecture rather than the file count:
37 declarations cover 412 files, producing 37 layers and 177 edges.

The vocabulary stops there deliberately. Both tags are needed to place a node
in the graph and label it, which is the whole of what these docs assert.
Anything further would be prose the generator cannot check, and a docs system
that cannot check its own claims is the thing being replaced.

Output is a portable bundle, not a website: `architecture.json` for consumers,
`architecture.md` for a single-pass read, generated pages, and 44 D2 diagrams
— an overview, a neighbourhood per layer showing what it depends on and what
depends on it, and a drill-down for each layer with children. Leaves get a
diagram too; they are where readers land.

`bundle/` is git-ignored build output. Committing it would mean reviewing every
change twice and resolving conflicts in generated files, and a stored copy
could go stale against the annotations that produced it.

CI runs `lint:arch-docs`, which fails on an unannotated source file, a layer id
implying an ancestor nobody declared, a duplicate declaration, a malformed tag,
a package configured for a language with no extractor, and any dependency
violating a rule in `architecture.config.ts`. Every check is a statement about
the graph. Four rules are enforced; the substantive one — `react` must not
depend on `ui` — already held, 0 imports against 235 the other way, so it locks
in a property the code already has.

`doc:architecture` is deliberately uncached: Turborepo hashes a package plus
its dependencies' task outputs, and the annotations this reads are source
comments in petrinaut and petrinaut-core, which are nobody's output. A cached
bundle would survive an annotation change and go quietly stale.

The authored-content pipeline is here and exercised by tests, but this branch
ships no `content/` directory and no renderer; both follow separately.

Copy link
Copy Markdown
Contributor

Looks great @kube! Just to confirm: the docs will be rendered as webpage in this PR right?

@kube
kube added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit db1dbe8 Aug 13, 2026
73 checks passed
@kube
kube deleted the cf/fe-1322-arch-docs-generator branch August 13, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team type/legal Owned by the @legal team

Development

Successfully merging this pull request may close these issues.

5 participants