| name | plan-error-catalogs |
| description | Error code constants, messages per WP |
| argument-hint | Invoked by Planner Coordinator - do not call directly |
plan-error-catalogs
Phase: 2 (Contract Generation)
Common contract: .github/skills/PLAN-SKILL-CONTRACT.md
Spec refs: FR-048, FR-049, FR-050, FR-039
This skill is dispatched by the Planner Coordinator during Phase 2. It reads the plan accumulator and generates language-specific error catalog contract files scoped per work package.
Input Contract (FR-023)
| # | Input | Description |
|---|
| 1 | skill_path | Path to this SKILL.md file |
| 2 | plan_dir | Path to .sdd/plans/ directory |
| 3 | contracts_dir | Path to .sdd/plans/contracts/ directory |
| 4 | spec_path | Path to the source spec file |
| 5 | spec_artifacts_dir | Path to spec companion artifacts |
| 6 | research_summary | Key findings from the research phase |
| 7 | target_language | Programming language for contract generation |
| 8 | patterns | Active plan-domain patterns to avoid |
| 9 | phase | Phase indicator (always 2 for this skill) |
Execution Sequence (FR-024)
- Read SKILL.md - Load this file for error catalog generation instructions
- Read plan state - Read README and all WP files to identify which WPs define error paths
- Read spec + artifacts - Read the spec and the companion artifact
error-catalog.<ext> from spec_artifacts_dir. Read spec and artifact files in parallel.
- Write contract files - Write
error-catalog.<ext> to contracts_dir/<WP-slug>/ per applicable WP
Step 1 - Identify Error-Bearing WPs
Read the plan accumulator. For each WP, determine if it:
- Introduces error handling for domain operations
- Defines API endpoints that return error responses
- Implements validation logic that produces error codes
- Handles external service failures
Skip WPs that:
- Are pure scaffolding with no error paths
- Only consume errors from other modules without defining new codes
Build the error-code-to-WP mapping:
AUTH_INVALID_TOKEN -> WP01
AUTH_EXPIRED_TOKEN -> WP01
USER_NOT_FOUND -> WP02
USER_ALREADY_EXISTS -> WP02
ORDER_INVALID_STATE -> WP03
Step 2 - Read Spec Companion Artifact (FR-049)
Check if spec_artifacts_dir contains an error-catalog.<ext> file. If it exists:
- Parse the spec-level error code definitions with HTTP mappings and messages
- These are canonical -- WP-level catalogs MUST match error codes and HTTP mappings
- Scope each WP's catalog to only the errors that WP introduces
If the spec artifact does NOT exist:
- Extract error codes from the spec's API design, security section, and FR error paths
- Note in a comment that these were derived from spec prose
Step 3 - Resolve Error Code Ownership (FR-050)
Process WPs in numerical order to determine error code ownership:
- First definer: The WP with the lowest number that introduces an error code owns the full definition
- Subsequent users: Later WPs that use the same error code import it from the owner
Build the ownership table:
| Error Code | Owner WP | Used by |
|---|
| AUTH_INVALID_TOKEN | WP01 | WP02, WP03 |
| USER_NOT_FOUND | WP02 | WP03 |
| VALIDATION_ERROR | WP01 | WP02, WP03, WP04 |
Step 4 - Generate Error Catalog Contract Files (FR-048)
For each applicable WP, generate <contracts_dir>/<WP-slug>/error-catalog.<ext>:
4a. Manifest Header (FR-039)
// Generated by: plan-error-catalogs skill
// Source spec: <spec_path>
// Work package: WP<NN>-<slug>
// Target language: <target_language>
// DO NOT EDIT MANUALLY -- regenerated on plan revision
4b. Error Code Constants/Enums (FR-048.1)
export const ErrorCodes = {
AUTH_INVALID_TOKEN: 'AUTH_001',
AUTH_EXPIRED_TOKEN: 'AUTH_002',
AUTH_INSUFFICIENT_PERMISSIONS: 'AUTH_003',
USER_NOT_FOUND: 'USER_001',
USER_ALREADY_EXISTS: 'USER_002',
USER_INVALID_EMAIL: 'USER_003',
} as const;
export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
from enum import Enum
class ErrorCode(str, Enum):
AUTH_INVALID_TOKEN = 'AUTH_001'
AUTH_EXPIRED_TOKEN = 'AUTH_002'
AUTH_INSUFFICIENT_PERMISSIONS = 'AUTH_003'
USER_NOT_FOUND = 'USER_001'
USER_ALREADY_EXISTS = 'USER_002'
USER_INVALID_EMAIL = 'USER_003'
Error code format:
- Use
DOMAIN_DESCRIPTIVE_NAME for constant names
- Use
DOMAIN_NNN for code values (string-based, not numeric)
- Group by domain (AUTH, USER, ORDER, etc.)
4c. HTTP Status Code Mappings (FR-048.2)
Map each error code to its HTTP status:
export const ERROR_HTTP_STATUS: Record<ErrorCode, number> = {
[ErrorCodes.AUTH_INVALID_TOKEN]: 401,
[ErrorCodes.AUTH_EXPIRED_TOKEN]: 401,
[ErrorCodes.AUTH_INSUFFICIENT_PERMISSIONS]: 403,
[ErrorCodes.USER_NOT_FOUND]: 404,
[ErrorCodes.USER_ALREADY_EXISTS]: 409,
[ErrorCodes.USER_INVALID_EMAIL]: 422,
};
ERROR_HTTP_STATUS: dict[ErrorCode, int] = {
ErrorCode.AUTH_INVALID_TOKEN: 401,
ErrorCode.AUTH_EXPIRED_TOKEN: 401,
ErrorCode.AUTH_INSUFFICIENT_PERMISSIONS: 403,
ErrorCode.USER_NOT_FOUND: 404,
ErrorCode.USER_ALREADY_EXISTS: 409,
ErrorCode.USER_INVALID_EMAIL: 422,
}
If the application is not HTTP-based (CLI, library), omit this mapping and note why.
4d. User-Facing Error Message Templates (FR-048.3)
export const ERROR_MESSAGES: Record<ErrorCode, string> = {
[ErrorCodes.AUTH_INVALID_TOKEN]: 'Authentication failed. Please log in again.',
[ErrorCodes.AUTH_EXPIRED_TOKEN]: 'Your session has expired. Please log in again.',
[ErrorCodes.AUTH_INSUFFICIENT_PERMISSIONS]: 'You do not have permission to perform this action.',
[ErrorCodes.USER_NOT_FOUND]: 'User not found.',
[ErrorCodes.USER_ALREADY_EXISTS]: 'A user with this email already exists.',
[ErrorCodes.USER_INVALID_EMAIL]: 'The email address provided is invalid.',
};
Message template rules:
- User-facing: clear, non-technical, no internal details
- May include placeholders:
'User {userId} not found'
- Must not expose stack traces, internal IDs, or system details
4e. Internal Log Message Templates (FR-048.4)
export const ERROR_LOG_MESSAGES: Record<ErrorCode, string> = {
[ErrorCodes.AUTH_INVALID_TOKEN]: 'Invalid token received. Token prefix: {tokenPrefix}',
[ErrorCodes.USER_NOT_FOUND]: 'User lookup failed. ID: {userId}, source: {source}',
[ErrorCodes.USER_ALREADY_EXISTS]: 'Duplicate user creation attempted. Email: {email}',
};
Internal messages:
- Include diagnostic details (IDs, sources, partial values)
- MUST NOT include full secrets, passwords, or tokens
- Include placeholders for contextual data
Step 5 - Handle Shared Error Codes (FR-050)
When the same error code is used by multiple WPs, apply the first-WP-defines pattern:
- The WP with the lowest number that introduces the error code owns the full definition in its own contracts directory
- All subsequent WPs that use the same error code import from the owner WP's contracts directory
- Do NOT write any error catalog files to
shared/ -- shared/ is reserved for config-schema files written by plan-cross-wp-validation only (Section 7.2)
Import syntax by language:
- TypeScript:
import { ErrorCodes } from '../WP01-slug/error-catalog';
- Python:
from ..wp01_slug.error_catalog import ErrorCode
Replace WP01-slug / wp01_slug with the actual slug of the owner WP.
Step 6 - 800-Line Block Compliance (FR-013)
If a WP's error catalog file exceeds 800 lines, split generation across multiple writes.
Constraints
- Output MUST be valid syntax in the target language
- Error codes MUST match the spec exactly -- do not invent codes not in the spec
- Do NOT include error handling logic (try/catch, middleware) -- only definitions
- Do NOT modify WP markdown files or spec artifacts -- read only
- Only write to
<contracts_dir>/<WP-slug>/error-catalog.<ext> -- do NOT write to shared/
- User-facing messages MUST NOT expose internal system details
- Use plain ASCII hyphens and straight quotes only -- no em dashes, smart quotes, or curly apostrophes