# PlanSpec JSON Schema

This document defines the complete schema for PlanSpec documents generated by the Planner agent. PlanSpecs are the authoritative source-of-truth for what will be built.

## Location

PlanSpec files are stored at: `.bober/specs/<specId>.json`

## Naming Convention

- `specId` format: `spec-<YYYYMMDD>-<slug>`
- The slug is derived from the title: lowercase, spaces replaced with hyphens, max 30 characters, no special characters
- Example: `spec-20260326-user-authentication`

## Full Schema

```json
{
  "specId": "string (required)",
  "version": "number (required, starts at 1, incremented on updates)",
  "createdAt": "string (required, ISO-8601 datetime)",
  "updatedAt": "string (required, ISO-8601 datetime)",
  "title": "string (required, 3-80 characters)",
  "description": "string (required, 2-3 sentences)",
  "mode": "string (required, one of: greenfield, brownfield)",
  "preset": "string (optional, e.g.: nextjs, react-vite, solidity, anchor, api-node, python-api)",
  "status": "string (required, one of: draft, needs-clarification, ready, in-progress, completed, abandoned)",

  "ambiguityScore": "number (optional, 0-10) — planner's self-rated ambiguity. >= 7 forces status='needs-clarification'.",

  "clarificationQuestions": [
    {
      "questionId": "string (required, e.g. 'Q1')",
      "category": "string (required, one of: scope | user-personas | data-model | tech-constraints | design-ux | integrations | non-functional | error-handling | integration-risk | pattern-conflict | regression-risk | other)",
      "question": "string (required, ends in '?')",
      "options": [
        { "label": "string (e.g. 'A')", "description": "string" }
      ],
      "recommendation": "string (optional, planner's suggested answer based on codebase evidence)",
      "ambiguityWeight": "number (optional, 0-10, how much this question contributes to overall ambiguity)"
    }
  ],

  "resolvedClarifications": [
    {
      "questionId": "string (required, matches a clarificationQuestions entry)",
      "answer": "string (required, free-form)",
      "resolvedAt": "string (required, ISO-8601)",
      "resolvedBy": "string (required, one of: user | planner)"
    }
  ],

  "assumptions": [
    "string — each assumption the planner is making, ideally with codebase evidence"
  ],

  "outOfScope": [
    "string — each item explicitly excluded from this plan"
  ],

  "features": [
    {
      "featureId": "string (required, format: feat-<index>)",
      "title": "string (required)",
      "description": "string (required)",
      "priority": "string (required, one of: must-have, should-have, nice-to-have)",
      "acceptanceCriteria": [
        "string — each criterion prefixed with AC<N>:"
      ],
      "dependencies": ["string — featureId references"],
      "estimatedComplexity": "string (required, one of: low, medium, high)"
    }
  ],

  "nonFunctionalRequirements": [
    {
      "category": "string (required, one of: performance, security, accessibility, reliability, maintainability)",
      "requirement": "string (required)",
      "verificationMethod": "string (required, how the evaluator verifies this)"
    }
  ],

  "techNotes": {
    "suggestedStack": "string (optional, only for greenfield projects)",
    "integrationPoints": ["string — external APIs or services"],
    "dataModel": "string (brief description of key entities and relationships)",
    "securityConsiderations": ["string — auth, validation, encryption, etc."],
    "existingPatterns": "string (optional, patterns from the codebase to follow)"
  },

  "sprints": [
    "string — contractId references, ordered by execution sequence"
  ],

  "metadata": {
    "estimatedTotalDuration": "string (e.g., '4-6 hours')",
    "riskLevel": "string (one of: low, medium, high)",
    "riskNotes": "string (optional, explanation of risk assessment)"
  }
}
```

## Field Descriptions

### Top-Level Fields

| Field | Description |
|-------|-------------|
| `specId` | Unique identifier for this spec. Generated once, never changes. |
| `version` | Integer version number. Incremented if the spec is revised after creation. |
| `createdAt` | ISO-8601 timestamp of initial creation. |
| `updatedAt` | ISO-8601 timestamp of last modification. |
| `title` | Human-readable feature title. Should be concise and descriptive. |
| `description` | 2-3 sentence summary of the feature and its user value. |
| `mode` | Must match the `project.mode` in `bober.config.json` (`greenfield` or `brownfield`). |
| `preset` | Must match the `project.preset` in `bober.config.json`, if set (e.g., `nextjs`, `solidity`, `anchor`). |
| `status` | Lifecycle state. `draft`: planner emitted a complete plan, no sprints run yet. `needs-clarification`: planner refused to fully decompose; user must answer the open `clarificationQuestions` before sprints can run. `ready`: clarifications resolved, pipeline may proceed. `in-progress`: at least one sprint has started. `completed`: all sprints done. `abandoned`: planner or user dropped this spec. |
| `ambiguityScore` | Planner's self-rating 0-10. If `>= 7`, the planner MUST set `status: "needs-clarification"` and emit at least one `clarificationQuestions` entry — the pipeline will refuse to run sprints from such a spec. |
| `clarificationQuestions` | Open questions awaiting user answers. Each unresolved entry blocks sprint execution. |
| `resolvedClarifications` | Question/answer history (both autonomous self-answers and user inputs via `bober plan answer`). |

### Features Array

Each feature represents a distinct, potentially independently valuable unit of functionality within the plan.

| Field | Description |
|-------|-------------|
| `featureId` | Unique within this spec. Format: `feat-1`, `feat-2`, etc. |
| `title` | Short feature name. |
| `description` | What this feature does and why it matters. |
| `priority` | `must-have`: Core functionality, plan fails without it. `should-have`: Important, but plan could ship without it. `nice-to-have`: Polish, optimization, extras. |
| `acceptanceCriteria` | Array of testable criteria. Each MUST be verifiable by the evaluator. Format: `"AC1: When [action], then [expected result]"`. |
| `dependencies` | Array of `featureId` values that must be implemented before this feature. Empty array if no dependencies. |
| `estimatedComplexity` | Rough complexity estimate to inform sprint sizing. `low`: straightforward, known patterns. `medium`: some unknowns or moderate logic. `high`: complex logic, integrations, or architectural decisions. |

### Acceptance Criteria Rules

Good criteria follow the Given-When-Then pattern:
- "AC1: When a user submits the registration form with a valid email and password, a new user account is created and the user is redirected to the dashboard."
- "AC2: When a user submits the form with an email that already exists, an error message 'This email is already registered' is displayed."

Bad criteria:
- "The feature works correctly" (not testable)
- "The code is clean" (subjective)
- "Performance is good" (not measurable)

### Non-Functional Requirements

| Field | Description |
|-------|-------------|
| `category` | One of: `performance`, `security`, `accessibility`, `reliability`, `maintainability`. |
| `requirement` | Specific, measurable requirement. E.g., "Page loads in under 2 seconds on 3G connection." |
| `verificationMethod` | How the evaluator can verify this. E.g., "Run Lighthouse audit and check Performance score > 80." |

### Tech Notes

| Field | Description |
|-------|-------------|
| `suggestedStack` | Only for greenfield projects. Describes the recommended tech stack. |
| `integrationPoints` | External services the feature depends on (APIs, OAuth providers, payment processors, etc.). |
| `dataModel` | Brief description of entities, their key fields, and relationships. Not a full schema -- just enough for the Generator to understand the domain. |
| `securityConsiderations` | Auth requirements, input validation needs, encryption, rate limiting, etc. |
| `existingPatterns` | For brownfield projects: patterns from the existing codebase that the Generator should follow. |

### Metadata

| Field | Description |
|-------|-------------|
| `estimatedTotalDuration` | Rough estimate of total implementation time across all sprints. |
| `riskLevel` | Overall risk assessment. `high` if the feature involves new integrations, architectural changes, or significant unknowns. |
| `riskNotes` | Explanation of risk factors. |

## Complete Example

```json
{
  "specId": "spec-20260326-user-auth",
  "version": 1,
  "createdAt": "2026-03-26T10:00:00Z",
  "updatedAt": "2026-03-26T10:00:00Z",
  "title": "User Authentication System",
  "description": "A complete user authentication system supporting email/password registration and login, with session management and protected routes. This enables the application to identify users and restrict access to authorized content.",
  "mode": "greenfield",
  "preset": "react-vite",
  "status": "draft",
  "assumptions": [
    "The application does not currently have any authentication system",
    "PostgreSQL is the database, as configured in the project",
    "Sessions will use HTTP-only cookies rather than localStorage for security",
    "Email verification is not required for initial registration (deferred to later)"
  ],
  "outOfScope": [
    "OAuth/social login (Google, GitHub, etc.)",
    "Two-factor authentication",
    "Password reset via email",
    "User profile management beyond basic info",
    "Admin user management dashboard"
  ],
  "features": [
    {
      "featureId": "feat-1",
      "title": "User Registration",
      "description": "Allow new users to create an account with email and password.",
      "priority": "must-have",
      "acceptanceCriteria": [
        "AC1: When a user navigates to /register, a registration form with email, password, and confirm-password fields is displayed.",
        "AC2: When a user submits valid registration data, a new user record is created in the database with a hashed password.",
        "AC3: When a user submits a registration with an already-used email, the form displays 'This email is already registered.'",
        "AC4: When a user submits a password shorter than 8 characters, the form displays 'Password must be at least 8 characters.'"
      ],
      "dependencies": [],
      "estimatedComplexity": "medium"
    },
    {
      "featureId": "feat-2",
      "title": "User Login",
      "description": "Allow existing users to authenticate with email and password.",
      "priority": "must-have",
      "acceptanceCriteria": [
        "AC1: When a user navigates to /login, a login form with email and password fields is displayed.",
        "AC2: When a user submits valid credentials, they are redirected to the dashboard and a session cookie is set.",
        "AC3: When a user submits invalid credentials, the form displays 'Invalid email or password.'"
      ],
      "dependencies": ["feat-1"],
      "estimatedComplexity": "medium"
    },
    {
      "featureId": "feat-3",
      "title": "Protected Routes",
      "description": "Restrict access to certain pages to authenticated users only.",
      "priority": "must-have",
      "acceptanceCriteria": [
        "AC1: When an unauthenticated user navigates to a protected route, they are redirected to /login.",
        "AC2: When an authenticated user navigates to a protected route, the page renders normally.",
        "AC3: A logout button is visible on all protected pages that destroys the session and redirects to /login."
      ],
      "dependencies": ["feat-2"],
      "estimatedComplexity": "low"
    }
  ],
  "nonFunctionalRequirements": [
    {
      "category": "security",
      "requirement": "Passwords must be hashed using bcrypt with a cost factor of at least 10.",
      "verificationMethod": "Inspect the registration code to verify bcrypt usage with appropriate cost factor."
    },
    {
      "category": "security",
      "requirement": "Session cookies must be HTTP-only, Secure, and SameSite=Strict.",
      "verificationMethod": "Inspect Set-Cookie headers in login response."
    },
    {
      "category": "accessibility",
      "requirement": "All form inputs must have associated labels and the forms must be keyboard-navigable.",
      "verificationMethod": "Verify label-input associations in HTML and test Tab navigation."
    }
  ],
  "techNotes": {
    "integrationPoints": [],
    "dataModel": "Single 'users' table with id (UUID), email (unique), password_hash, created_at, updated_at. Sessions stored server-side with express-session.",
    "securityConsiderations": [
      "Hash passwords with bcrypt before storage",
      "Rate-limit login attempts to prevent brute force",
      "Validate email format on both client and server",
      "Use CSRF protection for state-changing requests"
    ],
    "existingPatterns": "The project uses Express.js with middleware pattern. Follow existing route definition style in src/routes/."
  },
  "sprints": [
    "sprint-spec-20260326-user-auth-1",
    "sprint-spec-20260326-user-auth-2",
    "sprint-spec-20260326-user-auth-3"
  ],
  "metadata": {
    "estimatedTotalDuration": "3-5 hours",
    "riskLevel": "low",
    "riskNotes": "Standard auth implementation with well-known patterns. No external service dependencies."
  }
}
```
