sparrowhawk-labs/sparrowhawk

Deterministic TALL-stack app generation (Wright) and dataflow contract checking (Ward) from a single thin spec — declare what each page reads and writes, and catch violations before they bite.

Maintainers

Package info

github.com/sparrowhawk-labs/sparrowhawk

Homepage

pkg:composer/sparrowhawk-labs/sparrowhawk

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

dev-main 2026-07-18 01:25 UTC

This package is auto-updated.

Last update: 2026-07-18 01:25:51 UTC


README

Deterministic app generation and dataflow contract checking for the TALL stack.

"From apprentice to mage of the web."

Status

Alpha — a working vertical slice. The generator (Wright) and the checker (Ward) run end-to-end; APIs and spec vocabulary are not yet stable.

The idea: App = f(spec)

You write a small, finite spec — the Armature: your data schema plus, per page, what it reads and writes. From that:

  • Wright (generator) derives a plain Laravel/Livewire app — migrations, models, Livewire components, Blade views, tests, routes — as a pure function. Same spec in, byte-identical app out. No LLM, no randomness, no "regenerate and hope".
  • Ward (checker) verifies, statically, that the code — including everything you or your AI assistant edited afterwards — still honors the declared dataflow contract: code traffic ⊆ declared reads/writes ⊆ entity columns.

The generated code is plain Livewire, written the way a beginner would read it. You own it, edit it, and grow it; Sparrowhawk adds no runtime, no base classes to inherit, no new DSL beyond the spec itself (which is plain PHP).

return App::define('contacts')
    ->entity('Contact', fn (Entity $e) => $e
        ->string('name')
        ->string('email')
        ->string('phone'))
    ->page('contacts/index', fn (Page $p) => $p
        ->kind('index')
        ->reads('Contact', ['name', 'email', 'phone']))
    ->page('contacts/create', fn (Page $p) => $p
        ->kind('create')
        ->writes('Contact', ['name', 'email', 'phone']));

Quick start (the gen → edit → check loop)

composer install

bin/ward gen   playground/spec.php playground/app   # generate the baseline app
bin/ward check playground/spec.php playground/app   # → all green

# ... hand-edit a component or Blade view ...

bin/ward check playground/spec.php playground/app   # → ✗ violations / ⚠ flags

playground/SCENARIOS.md is a hands-on tour: each scenario is edit this → expect that → here's what it proves.

Ward is fail-closed

Ward does not infer intent. It resolves literal references (wire:model, {{ $contact->email }}, ->where('col'), ->col =, fillable arrays, …) against the closed vocabulary of your schema, and classifies each as a read or a write:

  • — all code traffic is inside the declared contract
  • ✗ violation — code exceeds the declaration (an undeclared read/write of a real column)
  • ⚠ flag — a construct Ward cannot resolve from the source text (dynamic column names, untyped receivers). It flags instead of silently passing. A missed defect that stays quiet is the one failure mode Ward is designed to never have.

Sensitive columns (->string('password', sensitive: true)) cannot be declared readable at all — exposure of a sensitive field is always a violation, on every page.

Does it work on apps Wright didn't generate?

Yes — that's the point of the contract being declared, not derived. verification/realworld/ contains a fully reproducible study against a third-party open-source app (the RealWorld/Conduit TALL implementation, Livewire 2.x, 9 pages):

  • After a curation pass (extract → human-approve declarations), the untouched app checks 0 violations.
  • 6 out of 6 seeded dataflow defects (undeclared writes, sensitive-field exposure, a renamed action, an over-reading view, a dynamic-property write) are caught — none silently pass.

To rerun it, clone the target app into verification/realworld/realworld-app (or set REALWORLD_APP) and run verification/realworld/realworld-harness.php.

What Sparrowhawk is not

  • Not LLM scaffolding — generation and checking are deterministic; results are reproducible and free to rerun
  • Not a lock-in platform — the output is ordinary Laravel/Livewire code; delete the spec and the app still stands
  • Not a new DSL to learn — the spec is plain PHP; the generated code uses only mainstream Livewire idioms
  • Not an admin-panel auto-generator (that's Filament's space) and not a UI library (that's pinion-ui)
  • Not a whole-app oracle — Ward guarantees the structural seams (screen ↔ component ↔ database). Method bodies, business logic, and visual design are deliberately yours.

Ecosystem

pinion (= primary feather, 風切り羽) is the wing-feather a sparrowhawk steers with — the plugin family that ships alongside the core.

Inspiration

The project draws its name from Ursula K. Le Guin's Earthsea — a story about a young wizard (Sparrowhawk) whose journey from apprentice to mage mirrors the path of a developer learning to wield powerful tools with care. The true name motif fits: Sparrowhawk asks you to name your app's structure precisely — and in return, the structure holds.

License

MIT. See LICENSE.

Sponsor

Crafted by Yakaze Tech Studio.