Skip to content

univeros/agent-spec — AI-readable manifests for every package #18

@tonydspaniard

Description

@tonydspaniard

Goal

Build univeros/agent-spec — a manifest generator that emits machine-readable descriptions of every framework package and every user-defined endpoint/command/event/job, so AI coding agents (Claude Code, Cursor, Codex, etc.) can understand the codebase in seconds without reading source.

Concretely: a CLI command that walks the framework's packages and the user's application, and writes per-package .agent/MANIFEST.md files plus a global index.

Why this is the framework's differentiator

The framework's current modernization already makes it the most agent-friendly PHP framework on the market (no magic, fully typed, single-pass middleware, readonly value objects). But "agent-friendly source code" still requires the agent to read the source. A manifest collapses that — the agent reads one structured file and knows what's available, what types flow through, what tests assert, where to look next.

This is the pitch:

univeros is the only PHP framework where an AI agent can be productive without reading a line of framework source.

Manifest format (per package)

Markdown, structured, deterministic. Example for univeros/http:

# univeros/http  ·  Altair\Http

**Purpose:** PSR-15 HTTP middleware pipeline + Action/Domain/Input/Responder application layer.

## Public contracts

| Interface | Method | Returns | Notes |
|---|---|---|---|
| `MiddlewareInterface` | `process(ServerRequestInterface, RequestHandlerInterface)` | `ResponseInterface` | Extends `Psr\Http\Server\MiddlewareInterface` + adds attribute constants |
| `ResponderInterface` | `__invoke(ServerRequestInterface, ResponseInterface, PayloadInterface)` | `ResponseInterface` | |
| `FormatNegotiatorInterface` | `getFromServerRequestUriPath`, `getFromServerRequestHeaderLine`, `getContentTypeByFormat` | various | |

## Concrete middleware (PSR-15)

- `IpAddressMiddleware` — populates `MiddlewareInterface::ATTRIBUTE_IP_ADDRESS` with detected client IPs
- `IpRestrictionMiddleware` — CIDR-based allow/deny based on the above attribute
- `CorsMiddleware`, `CsrfMiddleware`, `CacheMiddleware`, `ExceptionHandlerMiddleware`, `FormatNegotiatorMiddleware`, `BasicAuthenticationMiddleware`, `DigestAuthenticationMiddleware`, `TokenAuthenticationMiddleware`, `SpamBlockerMiddleware`, `DispatcherMiddleware`, `JsonContentMiddleware`, `FormContentMiddleware`, `SessionHeadersMiddleware`, `ActionMiddleware`

## Request attribute conventions

| Attribute | Set by | Consumed by | Type |
|---|---|---|---|
| `altair:http:ip-address` | `IpAddressMiddleware` | `IpRestrictionMiddleware`, app code | `list<string>` |
| `altair:http:action` | `DispatcherMiddleware` | `ActionMiddleware` | `Altair\Http\Base\Action` |
| `altair:http:format` | `FormatNegotiatorMiddleware` | responders | `string` |
| `altair:http:username` | `BasicAuth`/`DigestAuth` | app | `string` |
| `altair:http:exception` | `ExceptionHandlerMiddleware` | error responders | `Throwable` |

## Common patterns

### Build a middleware pipeline
```php
new Relay([
    new IpAddressMiddleware(),
    new IpRestrictionMiddleware($responseFactory, deny: ['10.0.0.0/8']),
    new CorsMiddleware($analyzer, $responseFactory),
    new ActionMiddleware($resolver, $responseFactory),
])

Short-circuit early

Middleware that fail closed (auth, IP block, CSRF) return a response built via ResponseFactoryInterface rather than calling $handler->handle().

Tests as documentation

  • tests/Http/Middleware/IpAddressMiddlewareTest.php — IP extraction conventions
  • tests/Http/Middleware/IpRestrictionMiddlewareTest.php — allow/deny precedence
  • tests/Http/Support/CidrMatcherTest.php — CIDR matching edge cases

Related packages

  • univeros/middleware — non-HTTP middleware substrate
  • univeros/sessionSessionHeadersMiddleware consumer
  • univeros/cookie — used by session + csrf middleware

Stability

PSR-15 single-pass migration completed 2026-05. No deprecated APIs remain.


The same structure for every package, plus a per-application manifest that lists user-defined endpoints, commands, events, and jobs (once the scaffolder from #3 / #5 lands).

## Why Markdown not JSON?

Two reasons:
- Agents are LLMs; they read Markdown faster than JSON. Tables + bullets compress better in context than nested JSON.
- Markdown renders on github.com for free, so it doubles as human-readable docs (the #15 docs link to manifests for "go deeper").

A `.json` sidecar can be emitted alongside the Markdown for tools that need structured data (search index, autocomplete extensions).

## Shape

src/Altair/AgentSpec/
├── Cli/
│ └── GenerateManifestCommand.php # bin/altair manifest:generate
├── Generator/
│ ├── PackageManifestGenerator.php # introspects one src/Altair//
│ ├── ApplicationManifestGenerator.php # introspects user app code
│ └── IndexGenerator.php # the top-level .agent/MANIFEST.md
├── Reflection/
│ ├── ContractScanner.php # finds interfaces under Contracts/
│ ├── AttributeScanner.php # finds class-level constants like ATTRIBUTE_*
│ └── TestFixtureScanner.php # collects test file paths per source class
├── Template/
│ └── *.md.twig # one per section (or built-in to PHP, doesn't have to be twig)
└── composer.json


CLI:

```bash
bin/altair manifest:generate              # writes to .agent/packages/*.md + .agent/MANIFEST.md
bin/altair manifest:generate --check     # exit 1 if manifests are out of date with source
bin/altair manifest:show http             # print one package's manifest to stdout

The framework:// resolver (stretch goal)

Optional helper: a tiny HTTP server (or CLI subcommand) that serves manifest contents via the framework:// URI scheme so agents can fetch sections without filesystem access. framework://packages/http/middleware → the Middleware section of http's manifest. Skip if it complicates the v1 scope.

Acceptance criteria

  • Running bin/altair manifest:generate from the monorepo root produces .agent/packages/<pkg>.md for all 16 framework packages
  • Each manifest covers: purpose, public contracts (table), concrete classes (list), attribute conventions where applicable, common patterns (2–4 code blocks), test-file references, related packages, stability note
  • Output is deterministic — running twice produces byte-identical files (no timestamps, no random ordering, sorted lists)
  • --check mode: compares regenerated content to disk; exits non-zero on diff; CI can use this to enforce manifests stay current
  • .agent/MANIFEST.md index lists every package with a one-line description + link
  • Tests:
    • tests/AgentSpec/PackageManifestGeneratorTest.php — fixture package → expected manifest
    • tests/AgentSpec/IndexGeneratorTest.php
    • Snapshot tests: store a known-good manifest for univeros/cookie, regenerate, diff

Out of scope

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions