---
phase: 01-config-and-command-foundation
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - paperforge/__init__.py
  - paperforge/config.py
  - tests/test_config.py
autonomous: true
requirements:
  - CONF-01
  - CONF-02
  - CONF-03
  - CONF-04
must_haves:
  truths:
    - "Environment variables can override vault and directory names without editing generated code."
    - "Existing paperforge.json files with top-level keys or nested vault_config still resolve correctly."
    - "The shared resolver exposes every path required by paperforge paths and downstream scripts."
  artifacts:
    - path: "paperforge/config.py"
      provides: "Shared configuration and path resolver"
      exports: ["DEFAULT_CONFIG", "ENV_KEYS", "resolve_vault", "load_vault_config", "paperforge_paths", "paths_as_strings"]
    - path: "tests/test_config.py"
      provides: "Resolver contract tests for precedence, compatibility, and path inventory"
  key_links:
    - from: "paperforge/config.py"
      to: "paperforge.json"
      via: "load_vault_config(vault)"
      pattern: "vault_config"
    - from: "paperforge/config.py"
      to: "process environment"
      via: "ENV_KEYS overlay"
      pattern: "PAPERFORGE_SYSTEM_DIR"
---

<objective>
Create the shared PaperForge Lite configuration resolver.

Purpose: Phase 1 depends on one importable path contract so workers, agent helpers, setup, validation, and command docs stop drifting.
Output: `paperforge/config.py` plus tests that lock config precedence, backward compatibility, and required path keys.
</objective>

<execution_context>
@C:/Users/Lin/.codex/get-shit-done/workflows/execute-plan.md
@C:/Users/Lin/.codex/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/01-config-and-command-foundation/01-CONTEXT.md
@.planning/phases/01-config-and-command-foundation/01-RESEARCH.md
@.planning/research/DEFECTS.md
@.planning/research/SUMMARY.md
@pipeline/worker/scripts/literature_pipeline.py
@skills/literature-qa/scripts/ld_deep.py

<interfaces>
Current worker functions that will later consume this resolver:
```python
def load_vault_config(vault: Path) -> dict:
def pipeline_paths(vault: Path) -> dict[str, Path]:
def run_status(vault: Path) -> int:
```

Current /LD-deep helper functions that will later consume this resolver:
```python
def _load_vault_config(vault: Path) -> dict:
def _paperforge_paths(vault: Path) -> dict[str, Path]:
def prepare_deep_reading(vault: Path, zotero_key: str, force: bool = False) -> dict:
```

Required environment keys per D-Configuration Hierarchy:
`PAPERFORGE_VAULT`, `PAPERFORGE_SYSTEM_DIR`, `PAPERFORGE_RESOURCES_DIR`, `paperforgeRATURE_DIR`, `PAPERFORGE_CONTROL_DIR`, `PAPERFORGE_BASE_DIR`, `PAPERFORGE_SKILL_DIR`, `PAPERFORGE_COMMAND_DIR`.
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Lock resolver precedence and path inventory tests</name>
  <files>tests/test_config.py</files>
  <read_first>
    - `.planning/phases/01-config-and-command-foundation/01-CONTEXT.md`
    - `.planning/phases/01-config-and-command-foundation/01-RESEARCH.md`
    - `pipeline/worker/scripts/literature_pipeline.py`
    - `skills/literature-qa/scripts/ld_deep.py`
  </read_first>
  <behavior>
    - Test that built-in defaults are `99_System`, `03_Resources`, `Literature`, `LiteratureControl`, `05_Bases`, `.opencode/skills`, `.opencode/command`.
    - Test that nested `paperforge.json` key `vault_config.system_dir` is honored per D-Configuration Hierarchy.
    - Test that top-level `paperforge.json` keys override nested keys for backward compatibility per D-Configuration Hierarchy.
    - Test that `PAPERFORGE_SYSTEM_DIR`, `PAPERFORGE_RESOURCES_DIR`, `paperforgeRATURE_DIR`, `PAPERFORGE_CONTROL_DIR`, `PAPERFORGE_BASE_DIR`, `PAPERFORGE_SKILL_DIR`, and `PAPERFORGE_COMMAND_DIR` override JSON values per CONF-01.
    - Test that explicit overrides passed to `load_vault_config(..., overrides={...})` override environment variables per D-Configuration Hierarchy.
    - Test that `paperforge_paths()` returns exactly these user-facing keys: `vault`, `system`, `paperforge`, `exports`, `ocr`, `resources`, `literature`, `control`, `library_records`, `bases`, `worker_script`, `skill_dir`, `ld_deep_script`.
  </behavior>
  <action>Create `tests/test_config.py` with pytest `tmp_path` tests that import from `paperforge.config`. The initial test run may fail before Task 2 creates the module; keep tests focused on pure functions and do not invoke real Zotero, OCR, Obsidian, or network services. Use concrete env dictionaries instead of mutating global `os.environ` where possible.</action>
  <verify>
    <automated>python -m pytest tests/test_config.py -q</automated>
  </verify>
  <acceptance_criteria>
    - `rg -n "PAPERFORGE_SYSTEM_DIR|PAPERFORGE_COMMAND_DIR|paperforge_paths|vault_config" tests/test_config.py` finds test coverage for env keys, path inventory, and JSON compatibility.
    - `python -m pytest tests/test_config.py -q` passes after Task 2.
  </acceptance_criteria>
  <done>`tests/test_config.py` proves the Phase 1 resolver contract for CONF-01, CONF-02, CONF-03, and CONF-04.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Implement shared config resolver</name>
  <files>paperforge/__init__.py, paperforge/config.py, tests/test_config.py</files>
  <read_first>
    - `tests/test_config.py`
    - `pipeline/worker/scripts/literature_pipeline.py`
    - `skills/literature-qa/scripts/ld_deep.py`
    - `paperforge.json`
  </read_first>
  <behavior>
    - `resolve_vault(cli_vault=None, env=None, cwd=None)` returns explicit CLI vault first, then `PAPERFORGE_VAULT`, then current directory or nearest parent containing `paperforge.json`, then `cwd`.
    - `load_vault_config(vault, env=None, overrides=None)` merges defaults, nested `vault_config`, top-level legacy keys, environment variables, and explicit overrides in the locked order from D-Configuration Hierarchy.
    - `paperforge_paths(vault, cfg=None)` returns absolute `Path` objects for all required D-Path Output keys.
    - `paths_as_strings(paths)` returns a JSON-serializable `dict[str, str]` without changing key names.
  </behavior>
  <action>Create `paperforge/__init__.py` and `paperforge/config.py`. In `config.py`, define `DEFAULT_CONFIG`, `ENV_KEYS`, `CONFIG_KEYS`, `read_paperforge_json(vault)`, `resolve_vault(cli_vault=None, env=None, cwd=None)`, `load_vault_config(vault, env=None, overrides=None)`, `paperforge_paths(vault, cfg=None)`, and `paths_as_strings(paths)`. Use only stdlib `json`, `os`, and `pathlib`. Do not load OCR secrets here. Do not mutate `os.environ`. Preserve existing `paperforge.json` top-level and nested `vault_config` behavior per CONF-04.</action>
  <verify>
    <automated>python -m pytest tests/test_config.py -q</automated>
  </verify>
  <acceptance_criteria>
    - `rg -n "DEFAULT_CONFIG|ENV_KEYS|def resolve_vault|def load_vault_config|def paperforge_paths|def paths_as_strings" paperforge/config.py` finds all exported contract symbols.
    - `python -m pytest tests/test_config.py -q` passes.
    - `python - <<'PY'\nfrom paperforge.config import paperforge_paths\nfrom pathlib import Path\nprint(sorted(paperforge_paths(Path('.').resolve()).keys()))\nPY` prints keys including `ld_deep_script`, `worker_script`, and `library_records`.
  </acceptance_criteria>
  <done>Shared resolver exists, is importable from the repo, and satisfies the locked precedence and path inventory decisions.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| process env -> resolver | User-controlled environment values become filesystem paths. |
| paperforge.json -> resolver | User-editable JSON becomes filesystem paths. |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-01 | Tampering | `paperforge/config.py` | mitigate | Resolve paths with `Path(...).expanduser().resolve()` for diagnostics and avoid shell command construction from path strings. |
| T-01-02 | Information Disclosure | `paths_as_strings()` | accept | This command intentionally prints local paths for the user; do not include OCR tokens or `.env` values in resolver output. |
</threat_model>

<verification>
Run `python -m pytest tests/test_config.py -q`.
</verification>

<success_criteria>
`paperforge.config` is the single tested contract for Phase 1 path resolution and contains no OCR doctor, PDF resolver, or Base redesign behavior deferred to later phases.
</success_criteria>

<source_audit>
## Multi-Source Coverage Audit

| Source | Item | Coverage |
|--------|------|----------|
| GOAL | Shared config resolver replaces manual path substitution | Covered by Plan 01, Plan 02, Plan 03, Plan 04 |
| REQ | CONF-01 | Covered by Plan 01 |
| REQ | CONF-02 | Covered by Plan 01 and Plan 02 |
| REQ | CONF-03 | Covered by Plan 01, Plan 03, Plan 04 |
| REQ | CONF-04 | Covered by Plan 01 and Plan 03 |
| REQ | CMD-01 | Covered by Plan 02 and Plan 04 |
| REQ | CMD-02 | Covered by Plan 03 and Plan 04 |
| REQ | CMD-03 | Covered by Plan 02 and Plan 04 |
| REQ | DEEP-02 | Covered by Plan 03 |
| RESEARCH | Add `paperforge/config.py` and tests | Covered by Plan 01 |
| RESEARCH | Add `paperforge/cli.py` and entry point | Covered by Plan 02 |
| RESEARCH | Preserve copied-vault worker/agent compatibility | Covered by Plan 03 |
| CONTEXT | D-Command Surface | Covered by Plan 02 and Plan 04 |
| CONTEXT | D-Configuration Hierarchy | Covered by Plan 01 |
| CONTEXT | D-Path Output | Covered by Plan 01 and Plan 02 |
| CONTEXT | D-Worker And Agent Compatibility | Covered by Plan 03 |
| CONTEXT | D-Packaging | Covered by Plan 02 |
| CONTEXT | Deferred `ocr doctor`, PDF resolver, Base redesign, full onboarding rewrite | Excluded from Phase 1 plans |
</source_audit>

<output>
After completion, create `.planning/phases/01-config-and-command-foundation/01-config-and-command-foundation-01-SUMMARY.md`
</output>
