From 8cd3e52c880f520183f33d220d89d5c3b1b7017e Mon Sep 17 00:00:00 2001 From: Boris Dev Date: Tue, 6 Oct 2026 23:14:10 +0000 Subject: [PATCH 1/3] Rename to graph-builder-spec (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boris, 2026-10-06, on the recommendation in #15: graph-builder-spec, on the grounds that #19's corrected subtitle — "A declaration layer over Pydantic Graph Builder" — is what licenses it, and that graph-builder-spec reuses Pydantic's own noun rather than inventing one. distribution workflow-workbench -> graph-builder-spec import package workflow_workbench -> graph_builder_spec frontend source frontend/workflow-workbench -> frontend/graph-builder-spec built island static/workflow-workbench.* -> static/graph-builder-spec.* No alias and no shim. A missed call site is an ImportError at the import, not a silent change of behaviour — the same choice 0.2.0 made for `NodeSpec(...)` and 0.3.0 for `coherence_check()`. ⚠️ CHANGELOG entries for SHIPPED releases are left as written, and the new entry says so. Those releases really were published as `workflow-workbench`; rewriting them would make history describe a name that did not exist at the time. ⚠️ The GitHub repo rename is a separate, outward-facing action and is Boris's: `gh repo rename graph-builder-spec`. Until it runs, the new clone URL does not resolve — which is why the two downstream repos' `uv lock` must be regenerated AFTER it, not in their rename PRs. 296 tests green locally, both generated tables regenerated and matching, and the quickstart command runs. No CI in this repo, so that is one local run. --- .claude/skills/implement-a-workflow/SKILL.md | 6 +-- CHANGELOG.md | 37 +++++++++++++++++++ README.md | 20 +++++----- docs/how-it-runs.md | 2 +- docs/migration-0.2.md | 2 +- docs/migration-0.3.md | 2 +- docs/parity.md | 32 ++++++++-------- docs/probe_builder_features.py | 10 ++--- examples/contestable.py | 2 +- examples/counter.py | 2 +- examples/greeting.py | 4 +- examples/ladder/stage10_no_basenode.py | 2 +- examples/ladder/stage1_bare.py | 2 +- examples/ladder/stage2_strategies.py | 2 +- examples/ladder/stage3_new_node.py | 2 +- examples/ladder/stage4_subgraph.py | 2 +- examples/ladder/stage5_battle.py | 2 +- examples/ladder/stage8_join.py | 2 +- examples/ladder/stage9_decision.py | 2 +- examples/local/extraction.py | 2 +- examples/parallel.py | 2 +- examples/subgraph.py | 2 +- .../package-lock.json | 0 .../package.json | 4 +- .../src/App.tsx | 0 .../src/Panel.tsx | 0 .../src/contract.ts | 2 +- .../src/layout.ts | 0 .../src/main.tsx | 6 +-- .../src/nodes.tsx | 0 .../src/panel.css | 0 .../src/styles.css | 0 .../tsconfig.json | 0 .../vite.config.ts | 8 ++-- .../__init__.py | 12 +++--- .../checks.py | 2 +- .../cli.py | 6 +-- .../devserver.py | 20 +++++----- .../diagram.py | 4 +- .../evals.py | 6 +-- .../graph_spec.py | 10 ++--- .../parity.py | 14 +++---- .../payload.py | 0 .../reference.py | 12 +++--- .../report.py | 4 +- .../serve.py | 18 ++++----- .../spec.py | 4 +- .../static/graph-builder-spec.css | 0 .../static/graph-builder-spec.js | 2 +- pyproject.toml | 10 ++--- tests/test_boundary_types.py | 10 ++--- tests/test_browser.py | 6 +-- tests/test_composed_node.py | 6 +-- tests/test_contestable.py | 2 +- tests/test_contract_parity.py | 8 ++-- tests/test_devserver.py | 4 +- tests/test_evals.py | 6 +-- tests/test_fan_out.py | 2 +- tests/test_greeting.py | 4 +- tests/test_island_browser.py | 4 +- tests/test_ladder.py | 16 ++++---- tests/test_parity.py | 10 ++--- tests/test_reference.py | 16 ++++---- tests/test_serve.py | 18 ++++----- tests/test_step_problem.py | 8 ++-- tests/test_subgraph.py | 6 +-- tests/test_transform_edge.py | 4 +- tests/test_vocabulary.py | 4 +- tests/test_workflow_spec.py | 8 ++-- uv.lock | 2 +- 70 files changed, 233 insertions(+), 196 deletions(-) rename frontend/{workflow-workbench => graph-builder-spec}/package-lock.json (100%) rename frontend/{workflow-workbench => graph-builder-spec}/package.json (84%) rename frontend/{workflow-workbench => graph-builder-spec}/src/App.tsx (100%) rename frontend/{workflow-workbench => graph-builder-spec}/src/Panel.tsx (100%) rename frontend/{workflow-workbench => graph-builder-spec}/src/contract.ts (96%) rename frontend/{workflow-workbench => graph-builder-spec}/src/layout.ts (100%) rename frontend/{workflow-workbench => graph-builder-spec}/src/main.tsx (81%) rename frontend/{workflow-workbench => graph-builder-spec}/src/nodes.tsx (100%) rename frontend/{workflow-workbench => graph-builder-spec}/src/panel.css (100%) rename frontend/{workflow-workbench => graph-builder-spec}/src/styles.css (100%) rename frontend/{workflow-workbench => graph-builder-spec}/tsconfig.json (100%) rename frontend/{workflow-workbench => graph-builder-spec}/vite.config.ts (82%) rename {workflow_workbench => graph_builder_spec}/__init__.py (86%) rename {workflow_workbench => graph_builder_spec}/checks.py (99%) rename {workflow_workbench => graph_builder_spec}/cli.py (81%) rename {workflow_workbench => graph_builder_spec}/devserver.py (95%) rename {workflow_workbench => graph_builder_spec}/diagram.py (98%) rename {workflow_workbench => graph_builder_spec}/evals.py (98%) rename {workflow_workbench => graph_builder_spec}/graph_spec.py (98%) rename {workflow_workbench => graph_builder_spec}/parity.py (96%) rename {workflow_workbench => graph_builder_spec}/payload.py (100%) rename {workflow_workbench => graph_builder_spec}/reference.py (96%) rename {workflow_workbench => graph_builder_spec}/report.py (99%) rename {workflow_workbench => graph_builder_spec}/serve.py (94%) rename {workflow_workbench => graph_builder_spec}/spec.py (99%) rename workflow_workbench/static/workflow-workbench.css => graph_builder_spec/static/graph-builder-spec.css (100%) rename workflow_workbench/static/workflow-workbench.js => graph_builder_spec/static/graph-builder-spec.js (99%) diff --git a/.claude/skills/implement-a-workflow/SKILL.md b/.claude/skills/implement-a-workflow/SKILL.md index 2cc7467..9e3d6dd 100644 --- a/.claude/skills/implement-a-workflow/SKILL.md +++ b/.claude/skills/implement-a-workflow/SKILL.md @@ -1,6 +1,6 @@ --- name: implement-a-workflow -description: Implement or modify the step bodies of a workflow-workbench GraphSpec. Use whenever asked to implement a step, fill in a strategy, add an arm, or make coherence_check() pass on a design in this repo. +description: Implement or modify the step bodies of a graph-builder-spec GraphSpec. Use whenever asked to implement a step, fill in a strategy, add an arm, or make coherence_check() pass on a design in this repo. --- # Implementing a workflow @@ -34,7 +34,7 @@ does **not** mean the stage is trivial. ## Your acceptance test ```python -from workflow_workbench import blocking +from graph_builder_spec import blocking findings = spec.coherence_check(strategy) blocking(findings) # empty == render() will succeed @@ -67,7 +67,7 @@ verdict — usually a missing return annotation. Fix the cause rather than filte ```bash uv run pytest -q -uv run python3 -m workflow_workbench.reference --check +uv run python3 -m graph_builder_spec.reference --check ``` Then say which findings remain and why, including the `NOT CHECKED` ones. **Do not report a diff --git a/CHANGELOG.md b/CHANGELOG.md index a30fecc..f3bed5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,43 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### ⛔ Breaking — renamed to `graph-builder-spec` + +```python +from workflow_workbench import GraphSpec # before +from graph_builder_spec import GraphSpec # after +``` + +| | before | after | +|---|---|---| +| distribution | `workflow-workbench` | `graph-builder-spec` | +| import package | `workflow_workbench` | `graph_builder_spec` | +| repository | `borisdev/workflow-workbench` | `borisdev/graph-builder-spec` | +| frontend source | `frontend/workflow-workbench/` | `frontend/graph-builder-spec/` | +| built island | `static/workflow-workbench.{js,css}` | `static/graph-builder-spec.{js,css}` | + +**No alias, no shim.** A missing call site is an `ImportError` at the import, not a silent change +of behaviour — the same choice 0.2.0 made for `NodeSpec(...)` and 0.3.0 for `coherence_check()`. + +**Why.** The shipped subtitle is *"A declaration layer over Pydantic Graph Builder"*, and +`graph-builder-spec` is that sentence. It reuses **their** noun rather than inventing one +(`domain-language.md` #1 — before inventing a noun, look for one that exists), and `-spec` +matches `GraphSpec` / `StepSpec` / `StrategySpec`, the vocabulary a reader meets in the first +code block. `workflow-workbench` said nothing about what the library wraps. + +Rejected: `pydantic-graph-spec` and `pydantic-graph-builder-spec` squat a namespace that belongs +to Pydantic's own packages; `graph-spec` collides with graph databases and GraphQL; +`workflow-spec` keeps a word the code uses and still says nothing about what is wrapped. + +⚠️ **Downstream repos track `main`, not a sha**, so this breaks their next `uv sync` until their +dependency name is updated. GitHub redirects the old clone URL; the old **distribution name** +does not exist. `nobsmed-v2` and `ai_computer_use` are updated alongside. + +⚠️ **Every `workflow-workbench` / `workflow_workbench` BELOW this entry is the former name**, left +as written. Those entries describe releases that really were published under it, and rewriting +them would make history describe a name that did not exist at the time. + + ### Added — `check_boundary_types`, the 13th rule A `GraphSpec`'s declared `input_type` / `output_type` are now compared against the variables the diff --git a/README.md b/README.md index 7827349..7e59062 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# workflow-workbench +# graph-builder-spec *A declaration layer over Pydantic Graph Builder.* @@ -13,7 +13,7 @@ step that composes the greeting and nothing complains: right type, right shape, normalization stage silently stops mattering. Declaring each value by NAME, not just by type, is what turns that into a finding. -Workflow Workbench is a [declaration layer](docs/glossary.md#declaration-layer) over Pydantic Graph Builder. You write the workflow's +Graph Builder Spec is a [declaration layer](docs/glossary.md#declaration-layer) over Pydantic Graph Builder. You write the workflow's shape and its data contracts as **data**, before any step exists — which is what makes that class of defect findable: @@ -192,8 +192,8 @@ Python 3.12 or newer, and [uv](https://docs.astral.sh/uv/). No API keys: the exa string handling and calls no model. ```bash -git clone https://github.com/borisdev/workflow-workbench -cd workflow-workbench +git clone https://github.com/borisdev/graph-builder-spec +cd graph-builder-spec uv sync --no-dev --extra evals uv run python3 -m examples.greeting ``` @@ -214,8 +214,8 @@ Everything it produces goes to the terminal; no files are written. Excerpt: ``` Both mermaid blocks above go past on the way — the specification, then the comparison. A browser -viewer is available as a separate process — `uv run python3 -m workflow_workbench.cli serve`, see -[`serve.py`](workflow_workbench/serve.py) — and nothing in the quickstart needs it. +viewer is available as a separate process — `uv run python3 -m graph_builder_spec.cli serve`, see +[`serve.py`](graph_builder_spec/serve.py) — and nothing in the quickstart needs it. ## The development sequence @@ -267,7 +267,7 @@ missed. Every one of these exists because it caught something that otherwise **ran and returned a -plausible answer**. Each check's docstring in [`checks.py`](workflow_workbench/checks.py) carries +plausible answer**. Each check's docstring in [`checks.py`](graph_builder_spec/checks.py) carries the measured case that produced it. **These are structural checks, not a proof of correctness.** A step that returns its input @@ -428,7 +428,7 @@ f.check # 'check_bindings' — which check produced it f.about # 'compose' — the node; 'source->target' for an edge; '' for the design f.blocking # True — False only for a `NOT CHECKED — …` stated gap -from workflow_workbench import blocking +from graph_builder_spec import blocking blocking(spec.coherence_check(unfinished)) # what `render()` refuses on, gaps excluded ``` @@ -522,7 +522,7 @@ wired, with no hook and no override, so a strategy can change what a node *does* what the workflow *is*. Every rule is in the generated table above, and the failure each one exists for is in that -check's docstring in [`checks.py`](workflow_workbench/checks.py) — one place, which is the point. +check's docstring in [`checks.py`](graph_builder_spec/checks.py) — one place, which is the point. This section used to repeat all of them in a hand-written table and had already drifted to 10 of 12; a test now refuses a second table of check names anywhere outside the generated block. @@ -629,5 +629,5 @@ uv run python3 -m examples.greeting # the walkthrough above uv run python3 docs/probe_api.py # the node-identity claims, against the real library uv run python3 docs/probe_builder_features.py # what the specification can and cannot express uv run python3 docs/probe_executor.py # every claim in docs/how-it-runs.md -uv run python3 -m workflow_workbench.parity --check # docs/parity.md is generated, not written +uv run python3 -m graph_builder_spec.parity --check # docs/parity.md is generated, not written ``` diff --git a/docs/how-it-runs.md b/docs/how-it-runs.md index a42488a..0f0111c 100644 --- a/docs/how-it-runs.md +++ b/docs/how-it-runs.md @@ -231,7 +231,7 @@ Theirs is an ordered list of markers, so one edge can do both: run -> ['ADA', 'GRACE'] Ours are separate types and no edge is both. See -[issue #2](https://github.com/borisdev/workflow-workbench/issues/2) for what a path-shaped +[issue #2](https://github.com/borisdev/graph-builder-spec/issues/2) for what a path-shaped `EdgeSpec` would cost. **Trigger to fix:** a design wanting `carries=list[Paper], delivers=pmid` on one wire, where the diff --git a/docs/migration-0.2.md b/docs/migration-0.2.md index 913d67a..a570df0 100644 --- a/docs/migration-0.2.md +++ b/docs/migration-0.2.md @@ -32,7 +32,7 @@ verification at the foot of this page. | | | |---|---| -| `from workflow_workbench import NodeSpec` | still works — `NodeSpec` is still exported | +| `from graph_builder_spec import NodeSpec` | still works — `NodeSpec` is still exported | | `def f(n: NodeSpec)` in your own code | still type-checks, and now accepts joins and decisions too | | `JoinSpec`, `DecisionSpec`, `EdgeSpec`, `MapEdgeSpec`, `TransformEdgeSpec`, `VariableSpec`, `StrategySpec`, `SubgraphBinding`, `GraphSpec` | unchanged | | `GraphSpec.nodes`, `.joins`, `.decisions`, `.edges` | unchanged. `nodes` still holds steps only | diff --git a/docs/migration-0.3.md b/docs/migration-0.3.md index 1f709eb..903f7ad 100644 --- a/docs/migration-0.3.md +++ b/docs/migration-0.3.md @@ -64,6 +64,6 @@ The prefix match is still correct and still supported. `blocking(findings)` is t `render()` uses, if you would rather not spell it out: ```python -from workflow_workbench import blocking +from graph_builder_spec import blocking blocking(spec.coherence_check(strategy)) ``` diff --git a/docs/parity.md b/docs/parity.md index a3e2f10..0e7cb48 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -21,11 +21,11 @@ everything is the engine with extra steps. `docs/probe_builder_features.py` introspects `GraphBuilder` and fails if any public method is unclassified, so the next thing Pydantic Graph ships turns it red instead of silently widening a gap this page describes as closed. The rows themselves are generated from -`workflow_workbench/parity.py`; `tests/test_parity.py` fails if this file and that one disagree. +`graph_builder_spec/parity.py`; `tests/test_parity.py` fails if this file and that one disagree. ```bash -python3 -m workflow_workbench.parity # print the table -python3 -m workflow_workbench.parity --check # exit 1 if this file is stale +python3 -m graph_builder_spec.parity # print the table +python3 -m graph_builder_spec.parity --check # exit 1 if this file is stale ``` ⚠️ `status` is per FEATURE, and a feature-by-feature table cannot express COMPOSITION. `map` is @@ -42,8 +42,8 @@ identically. ## Every builder feature, theirs beside ours - - + + ### `step` — **yes** @@ -55,7 +55,7 @@ async def double(ctx) -> int: return ctx.inputs * 2 ``` -Workflow Workbench: +Graph Builder Spec: ```python double = StepSpec("double", inputs=(n,), outputs=(n,)) @@ -74,7 +74,7 @@ g.add(g.edge_from(a).to(b)) g.add_edge(a, b, label='count') ``` -Workflow Workbench: +Graph Builder Spec: ```python EdgeSpec(source=a, target=b, carries=count) # `carries` IS the label @@ -88,7 +88,7 @@ Pydantic Graph: collect = g.join(reduce_sum, initial=0) ``` -Workflow Workbench: +Graph Builder Spec: ```python collect = JoinSpec("collect", reduce_sum, initial=0, @@ -107,7 +107,7 @@ Pydantic Graph: g.edge_from(g.start_node).map().to(square) ``` -Workflow Workbench: +Graph Builder Spec: ```python MapEdgeSpec(source=START, target=square, carries=numbers, delivers=number) @@ -125,7 +125,7 @@ d = d.branch(g.match(Urgent).to(escalate)) d = d.branch(g.match(Routine).to(research)) ``` -Workflow Workbench: +Graph Builder Spec: ```python route = DecisionSpec("route") @@ -146,7 +146,7 @@ async def split(ctx): yield w ``` -Workflow Workbench: +Graph Builder Spec: ```python split = StepSpec("split", inputs=(text,), outputs=(words,), streams=True) @@ -163,7 +163,7 @@ Pydantic Graph: g.edge_from(a).broadcast(lambda eb: [eb.to(x), eb.to(y)]) ``` -Workflow Workbench: +Graph Builder Spec: ```python EdgeSpec(source=a, target=x, carries=v) @@ -180,7 +180,7 @@ Pydantic Graph: g.edge_from(a, b).to(sink) ``` -Workflow Workbench: +Graph Builder Spec: ```python EdgeSpec(source=a, target=sink, carries=v) @@ -197,7 +197,7 @@ Pydantic Graph: d.branch(g.match(int, matches=lambda v: v > 10).to(big)) ``` -Workflow Workbench: +Graph Builder Spec: ```python # not declarable. Return a discriminating TYPE from a step instead: @@ -215,7 +215,7 @@ Pydantic Graph: g.edge_from(a).transform(lambda ctx: ctx.inputs.edges).to(b) ``` -Workflow Workbench: +Graph Builder Spec: ```python # fixed — part of the design, like a JoinSpec's reducer: @@ -237,7 +237,7 @@ class Increment(BaseNode[S, None, int]): return DoubleIt(...) ``` -Workflow Workbench: +Graph Builder Spec: ```python # no equivalent for the CLASS. All three things it is used FOR are declarable: diff --git a/docs/probe_builder_features.py b/docs/probe_builder_features.py index 1621355..1e04268 100644 --- a/docs/probe_builder_features.py +++ b/docs/probe_builder_features.py @@ -12,7 +12,7 @@ every check this library exists to provide — `coherence_check()` says so out loud (`NOT CHECKED — ... overrides build_pydantic_structure()`), and the middle section measures exactly that. -⛔ THE TABLE IS `workflow_workbench/parity.py`, AND IT IS CHECKED AGAINST THE REAL API. It was +⛔ THE TABLE IS `graph_builder_spec/parity.py`, AND IT IS CHECKED AGAINST THE REAL API. It was hand-written here once, from a grep of method names, and it MISSED FIVE — `stream`, `node`, `match_node`, `add_mapping_edge`, and the `matches=` predicate on `match` — while reading as a complete inventory of the gaps. A hand-maintained inventory of someone else's API is wrong the @@ -205,7 +205,7 @@ async def sink(ctx) -> int: print("can a GraphSpec DECLARE it? there is no second route — the declaration is the only wiring.") print("=" * 92 + "\n") -from workflow_workbench import ( # noqa: E402 +from graph_builder_spec import ( # noqa: E402 END, START, EdgeSpec, GraphSpec, StepSpec, StrategySpec, VariableSpec) n = VariableSpec("n", int) @@ -233,12 +233,12 @@ async def dbl(ctx) -> int: print() -# ── the table lives in workflow_workbench/parity.py — ONE definition ──────────────────────── +# ── the table lives in graph_builder_spec/parity.py — ONE definition ──────────────────────── # # ⛔ Two descriptions of one thing is the drift `.claude/rules/spec-as-code.md` exists to # prevent, so there is exactly one: `parity.py` is source, `docs/parity.md` is generated from it, # and this probe reads it rather than keeping its own copy. -from workflow_workbench.parity import FEATURES # noqa: E402 +from graph_builder_spec.parity import FEATURES # noqa: E402 ORDER = {"yes": 0, "partial": 1, "refused": 2, "cannot": 3, "plumbing": 4} print(f"{'GraphBuilder API':<32} {'declarable':>18} what to write instead") @@ -256,7 +256,7 @@ async def dbl(ctx) -> int: print("⚠️ `refused` and `cannot` are NOT the same as missing, and are kept apart on purpose:") print(" collapsing them into 'no' is how a design decision comes to read as a gap, and how the") print(" next person 'fixes' it. Full side-by-side examples: docs/parity.md, or") -print(" `python3 -m workflow_workbench.parity`.") +print(" `python3 -m graph_builder_spec.parity`.") # ── ⛔ and the check that stops this list going stale ──────────────────────────────────────── diff --git a/examples/contestable.py b/examples/contestable.py index 19a85d9..ec391b2 100644 --- a/examples/contestable.py +++ b/examples/contestable.py @@ -26,7 +26,7 @@ """ from __future__ import annotations -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, GraphSpec, StepSpec, StrategySpec, VariableSpec, blocking) # ── the values that move ──────────────────────────────────────────────────────────────────── diff --git a/examples/counter.py b/examples/counter.py index e65f5c6..5aee312 100644 --- a/examples/counter.py +++ b/examples/counter.py @@ -10,7 +10,7 @@ from dataclasses import dataclass -from workflow_workbench import END, START, EdgeSpec, GraphSpec, StepSpec, StrategySpec, VariableSpec +from graph_builder_spec import END, START, EdgeSpec, GraphSpec, StepSpec, StrategySpec, VariableSpec @dataclass diff --git a/examples/greeting.py b/examples/greeting.py index 2712b0d..0f38831 100644 --- a/examples/greeting.py +++ b/examples/greeting.py @@ -19,7 +19,7 @@ """ from __future__ import annotations -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, @@ -143,7 +143,7 @@ def main() -> None: print(f" {name:<14} {text!r:<22} {outs[0]!r:<26} {outs[1]!r}") print("\n6. the battle — same cases, same evaluator, scored:") - from workflow_workbench.evals import eval_battle + from graph_builder_spec.evals import eval_battle data = dataset() floor = eval_battle(spec, trim_only, trim_only, data) diff --git a/examples/ladder/stage10_no_basenode.py b/examples/ladder/stage10_no_basenode.py index 81b95e7..5979b83 100644 --- a/examples/ladder/stage10_no_basenode.py +++ b/examples/ladder/stage10_no_basenode.py @@ -23,7 +23,7 @@ from dataclasses import dataclass, field -from workflow_workbench import ( +from graph_builder_spec import ( END, START, DecisionSpec, diff --git a/examples/ladder/stage1_bare.py b/examples/ladder/stage1_bare.py index de264f3..bc9a300 100644 --- a/examples/ladder/stage1_bare.py +++ b/examples/ladder/stage1_bare.py @@ -22,7 +22,7 @@ from dataclasses import dataclass -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/examples/ladder/stage2_strategies.py b/examples/ladder/stage2_strategies.py index 17d1e71..f9f52b6 100644 --- a/examples/ladder/stage2_strategies.py +++ b/examples/ladder/stage2_strategies.py @@ -26,7 +26,7 @@ formal, pick, ) -from workflow_workbench import StrategySpec +from graph_builder_spec import StrategySpec async def pick_casual(ctx) -> str: diff --git a/examples/ladder/stage3_new_node.py b/examples/ladder/stage3_new_node.py index fd40185..6c98963 100644 --- a/examples/ladder/stage3_new_node.py +++ b/examples/ladder/stage3_new_node.py @@ -28,7 +28,7 @@ salutation, ) from examples.ladder.stage2_strategies import pick_casual -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/examples/ladder/stage4_subgraph.py b/examples/ladder/stage4_subgraph.py index 4388d59..64d09e5 100644 --- a/examples/ladder/stage4_subgraph.py +++ b/examples/ladder/stage4_subgraph.py @@ -30,7 +30,7 @@ translate_none, translate_shouty, ) -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/examples/ladder/stage5_battle.py b/examples/ladder/stage5_battle.py index 8a12a6a..01fd582 100644 --- a/examples/ladder/stage5_battle.py +++ b/examples/ladder/stage5_battle.py @@ -23,7 +23,7 @@ from examples.ladder.stage1_bare import Guest, HelloWorld, formal from examples.ladder.stage2_strategies import casual -from workflow_workbench.evals import eval_battle +from graph_builder_spec.evals import eval_battle class Warmth(Evaluator): diff --git a/examples/ladder/stage8_join.py b/examples/ladder/stage8_join.py index ff3e322..aaa3dfb 100644 --- a/examples/ladder/stage8_join.py +++ b/examples/ladder/stage8_join.py @@ -31,7 +31,7 @@ from pydantic_graph.join import reduce_list_append -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/examples/ladder/stage9_decision.py b/examples/ladder/stage9_decision.py index a9760fa..7c00339 100644 --- a/examples/ladder/stage9_decision.py +++ b/examples/ladder/stage9_decision.py @@ -24,7 +24,7 @@ from dataclasses import dataclass, field -from workflow_workbench import ( +from graph_builder_spec import ( END, START, DecisionSpec, diff --git a/examples/local/extraction.py b/examples/local/extraction.py index 2cdf260..e3d3db1 100644 --- a/examples/local/extraction.py +++ b/examples/local/extraction.py @@ -9,7 +9,7 @@ from dataclasses import dataclass -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/examples/parallel.py b/examples/parallel.py index 9fdf774..6e07d8b 100644 --- a/examples/parallel.py +++ b/examples/parallel.py @@ -22,7 +22,7 @@ from pydantic_graph.join import reduce_sum -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/examples/subgraph.py b/examples/subgraph.py index 677ee32..6c3343f 100644 --- a/examples/subgraph.py +++ b/examples/subgraph.py @@ -18,7 +18,7 @@ from pydantic import BaseModel -from workflow_workbench import ( +from graph_builder_spec import ( END, START, EdgeSpec, diff --git a/frontend/workflow-workbench/package-lock.json b/frontend/graph-builder-spec/package-lock.json similarity index 100% rename from frontend/workflow-workbench/package-lock.json rename to frontend/graph-builder-spec/package-lock.json diff --git a/frontend/workflow-workbench/package.json b/frontend/graph-builder-spec/package.json similarity index 84% rename from frontend/workflow-workbench/package.json rename to frontend/graph-builder-spec/package.json index 656b6f2..e6515f4 100644 --- a/frontend/workflow-workbench/package.json +++ b/frontend/graph-builder-spec/package.json @@ -1,9 +1,9 @@ { - "name": "workflow-workbench-island", + "name": "graph-builder-spec-island", "private": true, "version": "0.1.0", "type": "module", - "description": "React Flow island for a workflow report. Built output is committed into workflow_workbench/static; the Python package has no node step.", + "description": "React Flow island for a workflow report. Built output is committed into graph_builder_spec/static; the Python package has no node step.", "scripts": { "dev": "vite", "build": "tsc --noEmit && vite build", diff --git a/frontend/workflow-workbench/src/App.tsx b/frontend/graph-builder-spec/src/App.tsx similarity index 100% rename from frontend/workflow-workbench/src/App.tsx rename to frontend/graph-builder-spec/src/App.tsx diff --git a/frontend/workflow-workbench/src/Panel.tsx b/frontend/graph-builder-spec/src/Panel.tsx similarity index 100% rename from frontend/workflow-workbench/src/Panel.tsx rename to frontend/graph-builder-spec/src/Panel.tsx diff --git a/frontend/workflow-workbench/src/contract.ts b/frontend/graph-builder-spec/src/contract.ts similarity index 96% rename from frontend/workflow-workbench/src/contract.ts rename to frontend/graph-builder-spec/src/contract.ts index 0563393..2435c1b 100644 --- a/frontend/workflow-workbench/src/contract.ts +++ b/frontend/graph-builder-spec/src/contract.ts @@ -1,5 +1,5 @@ /** - * The wire contract — the TypeScript half of `workflow_workbench/payload.py`. + * The wire contract — the TypeScript half of `graph_builder_spec/payload.py`. * * ⚠️ HAND-KEPT IN STEP with the Pydantic model. There is no codegen, so `contract.test`-style * drift is possible; the guard is `tests/test_contract_parity.py`, which parses THIS file and diff --git a/frontend/workflow-workbench/src/layout.ts b/frontend/graph-builder-spec/src/layout.ts similarity index 100% rename from frontend/workflow-workbench/src/layout.ts rename to frontend/graph-builder-spec/src/layout.ts diff --git a/frontend/workflow-workbench/src/main.tsx b/frontend/graph-builder-spec/src/main.tsx similarity index 81% rename from frontend/workflow-workbench/src/main.tsx rename to frontend/graph-builder-spec/src/main.tsx index 11f20d5..b9aa193 100644 --- a/frontend/workflow-workbench/src/main.tsx +++ b/frontend/graph-builder-spec/src/main.tsx @@ -1,7 +1,7 @@ /** * Island bootstrap. It claims one
and nothing else. * - *
+ *
* * No router, no global state, no takeover of the page. If the div is not there, nothing happens. * Same contract as the webapp's causal-graph island, deliberately. @@ -14,14 +14,14 @@ import { createRoot } from "react-dom/client"; import { App } from "./App"; -const MOUNT_ID = "workflow-workbench-root"; +const MOUNT_ID = "graph-builder-spec-root"; function boot() { const el = document.getElementById(MOUNT_ID); if (!el) return; const reportUrl = el.dataset.reportUrl; if (!reportUrl) { - el.textContent = "workflow-workbench island: missing data-report-url"; + el.textContent = "graph-builder-spec island: missing data-report-url"; return; } createRoot(el).render(); diff --git a/frontend/workflow-workbench/src/nodes.tsx b/frontend/graph-builder-spec/src/nodes.tsx similarity index 100% rename from frontend/workflow-workbench/src/nodes.tsx rename to frontend/graph-builder-spec/src/nodes.tsx diff --git a/frontend/workflow-workbench/src/panel.css b/frontend/graph-builder-spec/src/panel.css similarity index 100% rename from frontend/workflow-workbench/src/panel.css rename to frontend/graph-builder-spec/src/panel.css diff --git a/frontend/workflow-workbench/src/styles.css b/frontend/graph-builder-spec/src/styles.css similarity index 100% rename from frontend/workflow-workbench/src/styles.css rename to frontend/graph-builder-spec/src/styles.css diff --git a/frontend/workflow-workbench/tsconfig.json b/frontend/graph-builder-spec/tsconfig.json similarity index 100% rename from frontend/workflow-workbench/tsconfig.json rename to frontend/graph-builder-spec/tsconfig.json diff --git a/frontend/workflow-workbench/vite.config.ts b/frontend/graph-builder-spec/vite.config.ts similarity index 82% rename from frontend/workflow-workbench/vite.config.ts rename to frontend/graph-builder-spec/vite.config.ts index cdfa8e8..67d11f0 100644 --- a/frontend/workflow-workbench/vite.config.ts +++ b/frontend/graph-builder-spec/vite.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from "vite"; // One island, one IIFE bundle, fixed filenames — the Python page hard-codes the two asset paths // and there is no manifest to read at request time. Output lands in the package's static tree and -// is COMMITTED: `pip install workflow-workbench` runs no node step, so an un-built island is a 404. +// is COMMITTED: `pip install graph-builder-spec` runs no node step, so an un-built island is a 404. // Same reasoning as the webapp's causal-graph island, deliberately. export default defineConfig(({ mode }) => ({ plugins: [react()], @@ -13,15 +13,15 @@ export default defineConfig(({ mode }) => ({ "process.env.NODE_ENV": JSON.stringify(mode === "development" ? "development" : "production"), }, build: { - outDir: "../../workflow_workbench/static", + outDir: "../../graph_builder_spec/static", emptyOutDir: false, sourcemap: false, lib: { entry: "src/main.tsx", name: "WorkflowWorkbenchIsland", formats: ["iife"], - fileName: () => "workflow-workbench.js", - cssFileName: "workflow-workbench", + fileName: () => "graph-builder-spec.js", + cssFileName: "graph-builder-spec", }, }, server: { port: 5175 }, diff --git a/workflow_workbench/__init__.py b/graph_builder_spec/__init__.py similarity index 86% rename from workflow_workbench/__init__.py rename to graph_builder_spec/__init__.py index 3d55071..f83ed32 100644 --- a/workflow_workbench/__init__.py +++ b/graph_builder_spec/__init__.py @@ -1,4 +1,4 @@ -"""workflow_workbench — one Pydantic graph design, many competing strategies. +"""graph_builder_spec — one Pydantic graph design, many competing strategies. GraphSpec the design: nodes + edges, as DATA StepSpec one role a strategy fills. The class you instantiate. @@ -10,10 +10,10 @@ blocking the findings that stop a render, i.e. all but the stated gaps spec.render(strategy) -> a real pydantic_graph.Graph -`evals` is imported separately (`from workflow_workbench.evals import eval_battle`) so that `render()` +`evals` is imported separately (`from graph_builder_spec.evals import eval_battle`) so that `render()` stays usable without an evaluation framework installed. """ -from workflow_workbench.checks import ( +from graph_builder_spec.checks import ( NOT_CHECKED, CoherenceFinding, blocking, @@ -31,9 +31,9 @@ check_variable_types, check_variables, ) -from workflow_workbench.diagram import diagram, diff_diagram -from workflow_workbench.graph_spec import GraphSpec -from workflow_workbench.spec import ( +from graph_builder_spec.diagram import diagram, diff_diagram +from graph_builder_spec.graph_spec import GraphSpec +from graph_builder_spec.spec import ( END, START, Bindable, diff --git a/workflow_workbench/checks.py b/graph_builder_spec/checks.py similarity index 99% rename from workflow_workbench/checks.py rename to graph_builder_spec/checks.py index 416dca5..f94b0b2 100644 --- a/workflow_workbench/checks.py +++ b/graph_builder_spec/checks.py @@ -14,7 +14,7 @@ from collections.abc import Callable, Iterable from typing import Any, TypeVar -from workflow_workbench.spec import ( +from graph_builder_spec.spec import ( Bindable, DecisionSpec, NodeSpec, diff --git a/workflow_workbench/cli.py b/graph_builder_spec/cli.py similarity index 81% rename from workflow_workbench/cli.py rename to graph_builder_spec/cli.py index 7b94aa2..e50a460 100644 --- a/workflow_workbench/cli.py +++ b/graph_builder_spec/cli.py @@ -1,4 +1,4 @@ -"""`python3 -m workflow_workbench.cli serve` — a real entry point, so a service unit need not embed +"""`python3 -m graph_builder_spec.cli serve` — a real entry point, so a service unit need not embed a snippet of Python in its ExecStart. ⚠️ `serve` refuses a non-localhost bind with no token; that check lives in `serve.serve()` and is @@ -11,7 +11,7 @@ def main(argv: list[str] | None = None) -> int: - ap = argparse.ArgumentParser(prog="workflow-workbench") + ap = argparse.ArgumentParser(prog="graph-builder-spec") sub = ap.add_subparsers(dest="cmd", required=True) s = sub.add_parser("serve", help="run the report viewer") @@ -20,7 +20,7 @@ def main(argv: list[str] | None = None) -> int: args = ap.parse_args(argv) if args.cmd == "serve": - from workflow_workbench.serve import serve + from graph_builder_spec.serve import serve serve(host=args.host, port=args.port) return 0 diff --git a/workflow_workbench/devserver.py b/graph_builder_spec/devserver.py similarity index 95% rename from workflow_workbench/devserver.py rename to graph_builder_spec/devserver.py index 76fd1c5..12f30ff 100644 --- a/workflow_workbench/devserver.py +++ b/graph_builder_spec/devserver.py @@ -1,6 +1,6 @@ """A dev tool: browse a GraphSpec's strategies in a browser. Phone-friendly. - from workflow_workbench.devserver import serve + from graph_builder_spec.devserver import serve serve({"case_build": (spec, [strategy_a, strategy_b, ...])}, port=8800) ## Why a JSON endpoint and not just an HTML page @@ -19,16 +19,16 @@ import inspect from typing import Any -from workflow_workbench.checks import blocking -from workflow_workbench.diagram import impl_name -from workflow_workbench.graph_spec import GraphSpec -from workflow_workbench.spec import StrategySpec, SubgraphBinding, is_sentinel +from graph_builder_spec.checks import blocking +from graph_builder_spec.diagram import impl_name +from graph_builder_spec.graph_spec import GraphSpec +from graph_builder_spec.spec import StrategySpec, SubgraphBinding, is_sentinel __all__ = ["spec_payload", "build_app", "serve"] def _endpoint_id(ep: Any) -> str: - from workflow_workbench.spec import _End, _Start + from graph_builder_spec.spec import _End, _Start if isinstance(ep, _Start): return "__start__" if isinstance(ep, _End): @@ -145,7 +145,7 @@ def spec_payload(spec: GraphSpec, strategies: list[StrategySpec]) -> dict[str, A -workflow-workbench — strategies +graph-builder-spec — strategies