PABLO
Docs
Documentation home

Getting started

  • What is PABLO?
  • Quickstart
  • Installation
  • Your first project
  • Your first task

Guides

  • Managing tasks
  • Dashboard
  • Analytics
  • Backups & restore
  • Troubleshooting

Concepts

  • How it works
  • Task states
  • Agents
  • Sync & rebase

Reference

  • Commands
  • Configuration
  • Providers
  • Storage

Contributing

  • Development

Contributing

Development

How the PABLO repository is organised, how the engine is built, and how to run the quality checks before sending a change. The engine is a Symfony application in app/; quality tools run through Castor.

Repository layout#

pablo/
├── app/                  # the Symfony application (Composer package "pablo")
│   ├── bin/pablo         # the pablo command (PABLO commands only)
│   ├── bin/console       # framework maintenance: cache:clear, lint:twig, debug:router…
│   ├── public/index.php  # the dashboard's front controller (pablo web)
│   ├── config/           # bundles, services, routes, packages/
│   ├── src/              # the engine
│   ├── templates/        # Twig: dashboard pages and components
│   ├── assets/           # dashboard JS, CSS, fonts, icons, vendored libraries
│   └── tests/            # PHPUnit, mirroring src/
├── bin/install.sh        # installer run from a checkout (and uninstall.sh)
├── examples/             # example global and project configuration
├── opencode/             # agent templates, the commit command, the skill (the single source for every runtime)
├── claude/               # generated, git-ignored: the Claude Code rendering of the command
├── copilot/              # generated, git-ignored: the Copilot agent files and skill
├── systemd/, launchd/    # scheduler and dashboard service definitions
├── site/                 # this website; site/installer is the curl | bash installer, served at /installer
└── castor.php            # quality tasks

Nothing is ever written outside the repository except the links the installer creates and the data in ~/.pablo/.

Engine modules#

Namespaces mirror folders (Pablo\ is app/src/).

FolderResponsibility
App/the kernel and the console application
Command/one Symfony Console command per subcommand, by topic: Task/, Sync/, Report/, System/, Internal/
Config/loading project YAML and merging defaults key by key
Domain/Task and Issue records, the State and Agent enums, Time, value objects such as DisplayCache, PrBadge, AgentActivity
StateMachine/the data-driven state machine and its single entry handler
Store/the state store and per-task locks
Poller/state polling and merge auto-close
Provider/external tools: Gh/ (pull requests, CI, reviews), Git/ (worktrees, sync), Confluence/, Tracker/ (GitHub, Jira, Linear)
Agents/agent launchers (Orca, OpenChamber, headless) behind one interface, chosen by agent_backend
Analytics/the JSONL event log, reader and aggregator
Listing/the terminal tables
Dashboard/, Controller/, Twig/Components/the dashboard
Doctor/, Dispatch/, Backup/, Support/preflight checks, the background job, backups, helpers

Architecture notes#

  • One kernel, three entry points. bin/pablo, bin/console and public/index.php share one FrameworkBundle kernel, whose container is cached in app/var/cache/<env>. PABLO_ENV defaults to dev so the cache is revalidated after a git pull.
  • Never resolve a PABLO_* variable (or HOME) when the container is compiled: the value would be frozen into the cache. Store and Agents resolve their paths in their constructors for this reason.
  • pablo --help lists PABLO commands only: the console application collects the pablo.command tag, not console.command.
  • Add a state: a case in Domain/State.php and a row in StateMachine::states(). Always change state through StateMachine::enterState(), never by setting the field.
  • Add an issue tracker: implement ProviderInterface and register it in ProviderRegistry.
  • Terminal and dashboard can't drift: both render the same value objects (PrBadge, AgentActivity) and use the same rules (Listing::isWaitingForFeedback(), Listing::stateRank()).
  • The dashboard never calls external tools while rendering: it reads the poller's DisplayCache. Analytics come from AnalyticsAggregator, shared with pablo stats.
  • Portability: the engine runs on Linux and macOS. tests/PortabilityTest.php fails if src/ mentions systemctl, journalctl, /etc/, /proc/ or /opt/.

Quality checks#

Run from the repository root, with the global Castor binary. The PHP tools are downloaded on first use.

bash
castor qa:test                          # PHPUnit
castor qa:phpstan                       # static analysis, level 8
castor qa:cs-fixer                      # fix coding style (@Symfony + @Symfony:risky)
castor qa:cs:check                      # check only (what CI runs)
castor qa:twig-cs-fixer                 # Twig templates
(cd app && composer exec phpunit tests/StateMachineTest.php)   # one test file
php app/bin/console lint:twig app/templates

PHPStan runs at level 8 over src, tests, config and public. Raise the level by fixing what it reports, never by ignoring errors or adding them to the baseline. php-cs-fixer runs with --path-mode intersection: a new top-level folder has to be added to both castor.php and app/.php-cs-fixer.php.

CI (.github/workflows/ci.yml) runs the tests, PHPStan and the coding-style check on every push to main and every pull request.

Tests#

  • Tests mirror the source tree: app/src/Store/Store.php is tested by app/tests/Store/StoreTest.php.
  • The engine uses dependency injection everywhere. Collaborators that touch the outside world sit behind interfaces (GhPrInterface, GitRepoInterface, ProcessRunnerInterface, ProviderRegistryInterface, AnalyticsInterface, AgentLauncherInterface), with fakes beside the tests: FakeAgents, FakeGit, FakeGhPr, FakeProcessRunner, StubProviders, FakeAnalytics.
  • FakeAgents is injected wherever a real agent launcher could be reached, so a test can never start a real opencode run, claude -p or copilot -p (a real, paid model call).
  • tests/Command/CommandTestBed.php wires the fake graph for command tests; tests/EngineGraph.php does the same for the poller, listing and dispatcher.
  • A test that boots the kernel must use the RestoresErrorHandlers trait around the boot, or PHPUnit flags it as risky.

Dashboard assets#

AssetMapper and an importmap: no Node, no build step, nothing fetched at runtime.

bash
php app/bin/console importmap:install            # after changing app/importmap.php
php app/bin/console ux:icons:import lucide:name  # add an icon, then commit it
  • app/assets/vendor/ must stay committed: the installer never runs importmap:install, so a missing file means a dashboard without JavaScript. (The root .gitignore only ignores /app/vendor/ for that reason.)
  • Icons are Lucide icons committed in app/assets/icons/, with online downloads disabled.
  • The fonts (Josefin Sans, JetBrains Mono, SIL Open Font License) are committed in app/assets/fonts/.
  • app/assets/styles/app.css themes Bulma after this website. Colour classes returned by TaskView and RebaseLogView (is-danger, is-success…) are mapped to soft tones in both themes; never use a bare tag is-light or is-grey (a test guards it).

This website#

The site lives in site/: the home page and assets as plain files, and one HTML fragment per documentation page in site/content/, listed in site/content/nav.yaml. A small PHP builder in site/build/ wraps each page in the layout, adds heading anchors and the “On this page” column, highlights code, checks every internal link, and writes the search index.

bash
castor site:build   # build into _site/
castor site:serve   # build and preview on http://127.0.0.1:8000

Inside a page, link to another page with href="page:states#reviews" (a typo fails the build), to a repository file with href="repo:app/src/Poller/Poller.php", and to a site asset with asset:assets/dashboard.png. Callouts are <aside class="callout note|tip|warning">. Pushing to main deploys the site with GitHub Pages.

Git discipline#

PABLO's own agents never commit or push on their own initiative; the only sanctioned paths are an explicit request from you, or /pablo-commit-and-pr. Contributions follow the same spirit: changes are delivered as a working tree or a pull request, and you decide when they land.

PreviousStorage

Edit this page on GitHub

On this page

  • Repository layout
  • Engine modules
  • Architecture notes
  • Quality checks
  • Tests
  • Dashboard assets
  • This website
  • Git discipline
PABLO

MIT licensed · made by Baptiste Leduc · GitHub · Docs