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..28fe536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,50 @@ 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}` | +| server env vars | `WORKFLOW_WORKBENCH_TOKEN` / `_STORE` | `GRAPH_BUILDER_SPEC_TOKEN` / `_STORE` | + +**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()`. + +⚠️ **The env-var rename fails CLOSED, which is why it is safe to do without an alias.** A stale +`WORKFLOW_WORKBENCH_TOKEN` is simply unset under the new name, and `serve()` raises `SystemExit` +rather than binding a non-localhost host unauthenticated — verified, not assumed. The process +refuses to start; it does not start without a token. `GRAPH_BUILDER_SPEC_STORE` falling back to +its default is a cosmetic path change, not an access-control one. + +**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..7ab1e56 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: @@ -98,7 +98,7 @@ so the difference you are looking at is the declaration layer and nothing else: | | step 1 | step 2 | |---|---|---| | **upstream**, unchanged — their [`visualize_graph.py`](https://pydantic.dev/docs/ai/graph/builder/) | `step_a` → `10` | `step_b` → `f'Result: {ctx.inputs}'` | -| **the control** — [`their_hello.py`](examples/ladder/their_hello.py): upstream's shape, a greeting instead of a number, and no workbench in the file | `pick` → `"Hello"` | `compose` → `f"{ctx.inputs}, {ctx.state.name}!"` | +| **the control** — [`their_hello.py`](examples/ladder/their_hello.py): upstream's shape, a greeting instead of a number, and none of this library in the file | `pick` → `"Hello"` | `compose` → `f"{ctx.inputs}, {ctx.state.name}!"` | | **ours** — [`greeting.py`](examples/greeting.py), the same workflow declared | `normalize` → a clean name | `compose` → `f"Hello, {name}!"` | Theirs is fine, and that is the point of keeping it: one graph with one implementation per step @@ -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. @@ -594,7 +594,7 @@ Proposals, not decisions — they are tracked, not quietly pending: | [`docs/parity.md`](docs/parity.md) | every Pydantic Graph builder feature, declarable or not | | [`docs/how-it-runs.md`](docs/how-it-runs.md) | their executor from the source, with a probe behind every claim | | [`examples/greeting.py`](examples/greeting.py) | the walkthrough above; beside it a counter, a fan-out, subgraphs, extraction | -| [`examples/ladder/their_hello.py`](examples/ladder/their_hello.py) | the control — their smallest program's SHAPE, adapted to a greeting, with no workbench in the file | +| [`examples/ladder/their_hello.py`](examples/ladder/their_hello.py) | the control — their smallest program's SHAPE, adapted to a greeting, with none of this library in the file | | [`examples/contestable.py`](examples/contestable.py) | four judgement-call stages, two strategies, nothing implemented and no score | Downstream of community requests for @@ -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..d379fab 100644 --- a/examples/greeting.py +++ b/examples/greeting.py @@ -7,7 +7,7 @@ It is a variation of `visualize_graph.py` from , their smallest complete builder program: two steps, the second formatting the first's output. -`examples/ladder/their_hello.py` keeps that original shape with no workbench in the file at all. +`examples/ladder/their_hello.py` keeps that original shape with none of this library in the file at all. The behaviour the design is supposed to deliver: @@ -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/ladder/their_hello.py b/examples/ladder/their_hello.py index fd052f6..57b415f 100644 --- a/examples/ladder/their_hello.py +++ b/examples/ladder/their_hello.py @@ -1,4 +1,4 @@ -"""Rung 0 — the control. Pydantic Graph alone, no workbench anywhere in the file. +"""Rung 0 — the control. Pydantic Graph alone, nothing from this library anywhere in the file. A variation of their `visualize_graph.py` from , which is the smallest complete program in their 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 79% rename from frontend/workflow-workbench/vite.config.ts rename to frontend/graph-builder-spec/vite.config.ts index cdfa8e8..59bf482 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", + name: "GraphBuilderSpecIsland", 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