Skip to content

build: publish process-graph alone, bundling the private building blocks - #41

Merged
Mearman merged 31 commits into
mainfrom
feat/one-public-package
Oct 11, 2026
Merged

Mearman merged 31 commits into
mainfrom
feat/one-public-package

Conversation

@Mearman

@Mearman Mearman commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Publishes one package, the unscoped process-graph, and makes every other workspace package private and bundled into it. Supersedes #34: its CLI commits are carried here, rebased onto main, with the fallout from the projection changes on main fixed in the CLI's tests.

process-graph is now built with tsdown (what cosmiconfig-extends, json-compose, trilean, config and most of the other ExaDev libraries use), which inlines the private packages' JavaScript and declarations and keeps every third-party library an import; deps.onlyBundle fails the build if anything else would be inlined. Following documents.js, each part is a subpath export:

Subpath Bundles
process-graph schema, projection and domain flat, DAG as dag (unchanged)
process-graph/layout layout
process-graph/source/github, /source/github/node source contract and GitHub issues adapter; the Node token port
process-graph/store, /store/node store contract and SQLite store; the node:sqlite driver
process-graph/store/wasm, /store/wasm/worker the browser store's page and worker halves
process-graph/react, /ui, /react-flow the React packages, kept apart because the styled components reuse the headless ones' names
process-graph/ui/style.css, /react-flow/style.css the compiled stylesheets, copied in by the build
process-graph/cli and the bin the command line library and the process-graph bin

zod and trilean are dependencies because the root entry needs them; everything only another subpath needs is an optional peer dependency, so installing the package for its root entry stays as light as before. The command line's text output escapes every character in the Unicode categories Cc, Cf, Zl, Zp, Cn and Co (except newline, tab, ZWNJ and ZWJ), so a synced title cannot act on the terminal, and on Unix the default store's directory and files are created and kept owner-only. The bin checks that the peers the command line imports resolve before loading it; when any are missing it prints one message naming them and the install command for the package manager that ran it, and exits with status 6, a new documented exit status, with no stack trace. The internal packages are devDependencies at exact versions, which is also what makes a change in one of them release process-graph: the orchestrator follows devDependencies and forces a patch on the dependent (the dry run on this branch shows the cascade). A feat in a private package therefore lands as a process-graph patch; that is ExaDev/semantic-release-workspace#80.

Private packages keep a version and a tag, which the orchestrator needs to find their last release, but now run only @semantic-release/npm, so they get no CHANGELOG.md updates and no GitHub Release. The orchestrator has no way to drop the tag.

Dependencies now come from a pnpm catalog: private manifests use catalog: and workspace:*, enforced by syncpack. The published manifest keeps literal exact versions, because @semantic-release/npm runs npm publish, which ships those specifiers verbatim; scripts/check-published-manifests.ts, added to deps:lint, fails on a protocol specifier or range there, on a version that differs from the catalog's or the sibling's, and on a catalog entry that is not exact. @exadev/process-graph-* and process-graph leave minimumReleaseAgeExclude.

The smoke test now packs the package, installs the tarball and its peers with pnpm into a throwaway project (npm 11.19.0, which the pinned Node ships, cannot resolve drizzle-orm 1.0.0-rc.4's optional effect peer at all), imports every subpath there, runs real work through each and the bin, and checks that the packed manifest has no protocol specifier, no shipped file names a private package, and both stylesheets resolve. A new browser integration test runs the store contract's conformance kit against the bundled store/wasm worker in Chromium. attw --profile esm-only and publint pass on the package.

The subpath modules are pure re-exports. @exadev/eslint-config 3.1.0 adds an entries option to exadev/barrel-policy (ExaDev/eslint-config#135), which packageLintConfig now passes through, so the package declares them as entry points and the rule still holds them to re-exports; no rule is turned off anywhere.

Every package was rebuilt uncached in CI, which pushed a work-growth case in projection's bounded-work test past Vitest's default per-test timeout under coverage; those cases now run without a clock bound, since they assert counted reads. Fixes #42.

The already-published @exadev/process-graph-* versions stay on the registry; deprecating them is left to a separate decision.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-11T08:16:08.302860Z 966bb26 Manual request

Security findings

Advisory findings (3)

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

The process-graph command line as a composition root over the other packages: sync from
GitHub issues into the SQLite store through sqlite-node, then ready, blocked, impact, path,
cycles, handoffs and why over the stored model, show for every view in Mermaid, DOT or the
projection as JSON, add and set to author a model and its run state, and validate for the
stored model or a model document.

runCli takes injected ports (output streams, store opener, sources by prefix, file reads,
facts and an abort signal) and resolves with an exit status, so the whole command surface runs
in tests and inside a host such as Switchboard without a process; nodePorts wires the real
ones, with the GitHub token from the adapter's environment helper. Each command's operation
returns plain data, every command writes JSON with --json, and exit statuses separate usage
errors, missing identifiers, invalid models and source refusals.

A sync merges every page into the model in one write; when a source fails part way the pages
already read are saved and the error prints the command that resumes with --after.
The bin runs the command line when it is loaded, so a bundler must not treat it as free of side
effects; every other module is.
The entry point now installs the command line as process-graph, delegating to the CLI
package's library entry with its own name and version. The library it exports stays
isomorphic, because the bin is excluded from the web-only typecheck program and exempt from
the import ban, since it is run rather than imported, and the CLI is not re-exported.
The pins named versions the workspace has since released past, so pnpm resolved
registry copies instead of linking the siblings, and syncpack reported the
drift.
The package is now built with tsdown, which inlines the workspace packages it
re-exports, their declarations included, and keeps every third-party library
an import. Besides the root entry, which is unchanged, each building block is
reachable through its own subpath: layout, source/github and its node token
helper, store, store/node, store/wasm and its worker module, and cli. Each
subpath loads only the chunks and libraries it reaches.

The workspace packages move to devDependencies, since nothing of them ships
but the inlined code. zod and trilean, which the root entry needs, are
dependencies; the libraries only other subpaths reach are optional peer
dependencies, so installing the package for its root entry stays light.
knip takes the package's entry points from its tsdown configuration. The bundle's
optional peers that only the built output imports are named, since knip traces
src/, which reaches them through the workspace packages the bundle inlines.
The headless hooks and components, the styled components and the React Flow
views are bundled as process-graph/react, process-graph/ui and
process-graph/react-flow. They stay three subpaths because the styled
components reuse the headless components' names. The bundler carries no CSS
across, so the two compiled stylesheets are copied beside their subpaths and
exported as ui/style.css and react-flow/style.css, and stylesheets are listed
as side effects. React, React DOM, React Flow and Vanilla Extract are optional
peer dependencies.
A scenario through a decision's branch now projects that branch as an alt
fragment, which the Mermaid emitter draws as a block and the DOT emitter as a
message label prefix.
Projected flowchart edges and state machine transitions now carry the model
transitions they stand for, so the emitter fixtures state them.
Only process-graph is published; it bundles the others, so they need no place on
the registry of their own. The versions already published stay where they are.
A private package now runs only the npm plugin, which bumps its version and
skips publishing, so it writes no changelog and creates no GitHub Release. It
keeps its tag, which the orchestrator reads to find its last release, and its
release still cascades to process-graph through devDependencies.
…workspace

With only process-graph published, the private packages need not name
installable versions. Each now takes its third-party versions from the one
catalog in pnpm-workspace.yaml and its siblings through workspace:*, so a
version is written once. The published manifest keeps exact literals, because
@semantic-release/npm publishes with npm publish, which ships a catalog or
workspace protocol specifier verbatim.

syncpack enforces the catalog and the workspace protocol in private manifests.
scripts/check-published-manifests.ts, now part of deps:lint, fails on a
protocol specifier or a range in a published manifest, on a version there
that differs from the catalog's or the sibling's own, and on a catalog entry
that is not exact. The process-graph packages leave the install age
exclusions, since nothing installs them from the registry any more.
…alls it

The smoke test now packs the package with pnpm pack, installs the tarball and
its peer dependencies with npm into a throwaway project outside the repository,
and imports every subpath from there, running real work through each: a model
projected and reasoned over, a flowchart laid out, a store saved and loaded on
node:sqlite, a command run through the Node ports, and the bin's help and
version. It also checks that the packed manifest ships no catalog or workspace
specifier, that no shipped file names a private workspace package, and that
both stylesheets resolve through their subpaths.
…m store

A browser integration test starts workers on the bundle's store/wasm/worker
entry and connects through its store/wasm entry in Chromium, with no
cross-origin isolation, and runs the store contract's conformance kit and a
reopen check against them. Integration tests now wait for the package's own
build, since this one runs its built output.
… packages

The package table separates process-graph from the internal packages, the
release section says what a private package gets, and adding a package now
means a private package exposed through a subpath of process-graph, with no
npm name to reserve.
Each internal package's README drops its npm and release badges and says where
process-graph exposes it.
npm 11.19.0, which the pinned Node ships, fails with ERESOLVE on drizzle-orm
1.0.0-rc.4's optional effect peer range even when drizzle-orm is installed on
its own; npm 11.19.1 resolves it.
They count reads at a size and its double, so their duration is the machine's,
not a property of the code. The largest diffFlowchart case sat near the
default per-test timeout under coverage and crossed it whenever CI ran every
package uncached.

Fixes #42
The process-graph bin exits with it when the optional peers the command line
imports are not installed. exit-codes is its own entry so the bin can read the
status without loading the command line.
Before loading the command line, the bin checks that the optional peers it
imports resolve. When any do not, it prints one message naming them and the
command that installs them at their declared versions with the package manager
that ran it (npm when none did), and exits with the missing-peers status
without a stack trace. The smoke test installs the packed package without its
peers and checks that message and status.
@Mearman
Mearman force-pushed the feat/one-public-package branch from fa9827d to 698f86c Compare October 11, 2026 05:11
…napshot that exists

The lockfile resolved @vitest/browser-playwright for process-graph against a
vite peer variant with no snapshot entry, so a clean install on Linux left the
package unlinked.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

1 similar comment
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector 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.

🛡️ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 1afd16a454

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread packages/targets/graph-cli/src/render.ts
…d line writes

Synced titles, labels and identifiers reached the terminal verbatim, so an ESC
sequence in an issue title could clear the screen or set the clipboard (OSC
52), and a bidirectional override could reorder what is shown. Every write now
crosses one boundary that writes C0 controls other than line feed and tab, DEL,
C1 controls and the Unicode bidirectional and invisible format controls as
visible escapes of their code points (\u{1b}). The Mermaid and DOT emitters
escape inside their own label escaping, so diagrams stay well-formed, and
--json writes the same characters as JSON escapes, leaving parsed values
unchanged.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

1 similar comment
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector 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.

🛡️ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 8d240651ce

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread packages/targets/graph-cli/src/node.ts Outdated
The default data directory, the database and the -wal and -shm files took the
process umask, so under the usual umask other users on the host could read
synced titles and relationships from a predictable path. On Unix the command
line now creates its default directory, and the directories it creates above
it, as 0700, and creates the database as 0600 before SQLite opens it, which
gives the sidecars the same mode. An existing default store readable by others
is narrowed, and the port returns a warning the command line prints once on
standard error. At a user-named path only a database the command line creates
is made private; the user's directory and files are left alone.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

1 similar comment
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector chatgpt-codex-connector 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.

🛡️ Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 176c3ea4bb

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread packages/targets/graph-cli/src/terminal-text.ts Outdated
Enumerated ranges missed bidirectional controls such as U+061C, the Arabic
letter mark, which reached human and --json output raw. Everything in Cc, Cf,
Zl, Zp, Cn and Co is now escaped, by code point, apart from line feed, tab and
the zero-width non-joiner and joiner, which emoji sequences and several scripts
need. Escapes beyond the Basic Multilingual Plane are written whole in text and
as surrogate pairs in JSON.
It types the React and Next.js configs as the arrays they are, so the React
config is spread rather than nested. eslint-plugin-json-canonical, which it
now depends on, is ExaDev's own and joins the install age exclusions.
packageLintConfig takes the package's other entry files and passes them to
exadev/barrel-policy's entries option, so process-graph's subpath modules are
held to re-exports like src/index.ts instead of having the rule turned off.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

1 similar comment
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: f8cf684f06

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: f8cf684f06

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Only a main thread may set the umask, and Vitest runs tests in worker threads,
under Stryker always, so the dry run failed. A probe script opens the default
store in a process of its own under umask 022 and 000 and prints the modes it
made; the narrowing and user-path cases need no umask and stay in process.
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

1 similar comment
@Mearman

Mearman commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 966bb263e1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@Mearman
Mearman merged commit cf86503 into main Oct 11, 2026
14 checks passed
@Mearman
Mearman deleted the feat/one-public-package branch October 11, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bounded-work's largest diffFlowchart case runs close to the default test timeout under coverage

1 participant