# wp-materialize – Agents Specification ## Purpose `wp-materialize` is an **automation compiler** that materializes specified Markdown files in Git repositories (or local directories) into WordPress posts. Git / filesystem state is the **single source of truth**; WordPress is a **derived, materialized view**. The system is **declarative, atomic, incremental, and fail-fast**. It never guesses intent, never partially updates WordPress, and never mutates state unless correctness is proven ahead of time. This document is the **authoritative agent-facing spec** to be fed to Codex or other build agents. --- ## Core Principles (Non-Negotiable) 1. **Git / Filesystem as Source of Truth** WordPress content must exactly reflect declared Markdown sources and manifests. 2. **Declarative Configuration Only** No implicit discovery, no heuristics, no inference. 3. **Atomic Execution** A full dry-run validation must succeed before *any* WordPress mutation occurs. 4. **Incremental Updates** Only content whose source timestamp is newer than its cached materialization timestamp may be updated. 5. **Fail Fast, Fail Loud** Any configuration, validation, or conversion error aborts the entire run. --- ## High-Level Architecture The system operates in two strictly separated phases: ### Phase 1: Pure Evaluation (Dry Run) * Read global config * Discover and load repositories / directories * Load and validate all `.wp-materialize.json` manifests * Resolve inheritance (categories, tags, subdirectories) * Convert Markdown → HTML (in-memory only) * Resolve titles, timestamps, taxonomy * Determine incremental update set * Detect *all* errors **If any error occurs, execution stops here.** ### Phase 2: Side-Effect Application * Create missing WordPress categories * Create or update WordPress posts * Update cached timestamps *only for successfully applied posts* --- ## Global Configuration Location: ``` .config/wp-materialize/config.json ``` Responsibilities: * WordPress root directory (where `wp` CLI is executed) * Repository storage directory * List of Git repositories to clone / pull * List of non-git directories to manage ### Runtime State (Separate from Config) Mutable runtime state must be stored separately, e.g.: ``` .config/wp-materialize/state.json ``` State includes: * Last successful materialization timestamp per post * Cached per-post source timestamps Config must remain declarative and diffable. --- ## Repository and Directory Rules * Each managed directory **must** contain a `.wp-materialize.json` manifest. * Any directory listed under `subdirectories` **must** contain its own manifest. * Missing manifests are **hard errors**. * No implicit recursion is allowed. --- ## Per-Directory Manifest: `.wp-materialize.json` Each manifest defines a **scope boundary**. ### Top-Level Fields #### `categories` ```json { "content": ["Systems", "Infrastructure"], "inherit": true } ``` * `content`: array of category paths * `inherit: true` → append to parent effective categories * `inherit: false` → override parent categories entirely #### `tags` ```json { "content": ["automation", "wordpress"], "inherit": true } ``` Semantics identical to `categories`. #### `subdirectories` ```json { "content": ["design", "notes"], "inherit": true } ``` * Controls traversal explicitly * Included subdirectories **must** have their own manifest * `inherit: false` cuts traversal --- ## File-Level Configuration Each file listed under `files` represents a WordPress post. ```json "files": { "post.md": { "title": "Explicit Title", "categories": { "content": ["Overrides"], "inherit": false }, "tags": { "content": ["extra"], "inherit": true } }, "essay.md": { "use_heading_as_title": { "level": 1, "strict": true } } } ``` ### Title Rules * If `use_heading_as_title` is specified: * Extract the specified heading level * Use it as the WordPress post title * Remove that heading from the body * Promote remaining headings by one level * If `strict: true`, exactly one matching heading must exist * Otherwise, `title` **must** be provided --- ## Markdown → HTML Conversion * Conversion occurs **only during dry run** * No HTML is written or sent to WordPress during evaluation * Conversion errors are fatal --- ## Category Materialization * Categories are treated as **hierarchical paths** * If a declared category path does not exist in WordPress: * It is **automatically created** during the apply phase * Category creation: * Must be planned during dry run * Must occur before post updates Tags are **not** auto-created. --- ## Timestamps and Incremental Updates ### Timestamp Sources * Git repository: * Use Git commit timestamps * Non-git directory: * Use filesystem timestamps The source of timestamps must be **deterministic per repository**. ### Cached Metadata * Each post stores a cached source timestamp representing the last **successful** materialization * Failed runs **must not** update cached timestamps ### Incremental Rule On each run: * Compare current source timestamp vs cached timestamp * Only posts where `source_timestamp > cached_timestamp` are eligible for update * Unchanged posts are treated as no-ops --- ## Post Identity Each WordPress post must store stable metadata: ``` _wp_materialize_source = : ``` This identity is used for: * Idempotent updates * Safe renames * Incremental comparison --- ## Atomicity Guarantee * If **any** dry-run validation fails: * No WordPress calls are executed * No categories are created * No cached timestamps are updated * Apply phase executes only after full validation succeeds --- ## Error Handling All errors are fatal: * Missing manifests * Invalid inheritance * Invalid Markdown * Missing or ambiguous titles * Invalid category/tag resolution * Timestamp resolution failures No warnings. No partial success. --- ## Implementation Notes * Language: **Python** * The implementation must prioritize: * Determinism * Readable error messages * Testable pure functions for evaluation phase This document is the **contract**. Implementation must not relax or reinterpret it.