Implementation Plan: Phase 3 Charter Synthesizer Pipeline

Branch: main (planning and eventual merge target) | Date: 2026-04-17 | Spec: /Users/robert/spec-kitty-dev/charter2/spec-kitty/kitty-specs/phase-3-charter-synthesizer-pipeline-01KPE222/spec.md Input: spec.md · 20 FR, 10 NFR, 12 C · 8 user scenarios · 7 edge cases

Summary

Deliver a two-layer Charter Synthesizer that turns charter interview answers, shipped doctrine, and the shipped Doctrine Reference Graph (DRG) into project-local directives, tactics, and styleguides. Artifact content (directive / tactic / styleguide YAMLs and the project DRG overlay) lands under .kittify/doctrine/ — the tree the existing doctrine repositories and _drg_helpers already recognise as a project layer. Synthesis bookkeeping (per-artifact provenance sidecars, the commit-marker manifest, and ephemeral staging) lands under .kittify/charter/ so doctrine consumers see only doctrine and bundle consumers see only bundle state. Orchestration — input normalization, target selection, write staging, validation, path-guard enforcement, topic resolution, manifest commit — is fully deterministic. The actual prose generation is model-driven but lives behind a narrow sync adapter interface with fixture-backed test coverage. The whole pipeline fails closed on schema, DRG, or path-guard violations using a stage-and-promote atomicity model with manifest-last as the authoritative commit marker.

Scope is intentionally bounded: directive, tactic, styleguide only; WP3.1/3.2/3.6/3.7/3.8 only; structured --topic selectors only; no code-reading, no URL fetching, no free-text topic rewriting, no cross-repo visibility, no migration tooling.

Technical Context

Language/Version: Python 3.11+ (existing spec-kitty baseline) Primary Dependencies (existing, reused): typer (CLI), rich (console), ruamel.yaml (YAML parsing with preserved ordering/anchors), pydantic v2 (schemas), blake3 (already in charter for hashing), pytest (tests), mypy strict (type-check) New Dependencies: None. No new runtime or test dependency is added by this tranche. The production model adapter's backing client (governed by ADR-6, #521) is a soft dependency and lives outside this mission's merge gate. Storage: Filesystem only. New paths under .kittify/doctrine/ (synthesized content — directives, tactics, styleguides, project DRG graph), .kittify/charter/ (synthesis bookkeeping — provenance, manifest, staging), and tests/charter/fixtures/synthesizer/ (test fixtures). No database, no network, no queue. Testing: pytest, mypy --strict, existing charter/doctrine test harnesses. 100% of tests run with the fixture adapter; no network calls in CI. Target Platform: Local developer machines + CI (Linux + macOS + Windows per existing spec-kitty matrix; the staging/atomic-promote model uses os.replace which is POSIX-on-POSIX + atomic-rename-on-Windows). Project Type: Single project (extends existing src/charter/ package). Performance Goals:

Constraints:

Scale/Scope: Small. Tens of artifact files per project at steady state. Dozens of DRG overlay edges. Individual artifact bodies measured in kilobytes, not megabytes. This is not a throughput problem; it is a correctness and determinism problem.

  • Full synthesis on a ≤10-answer interview (fixture adapter): < 30s wall-clock (NFR-002).
  • Bounded resynthesize --topic: < 15s wall-clock (NFR-003).
  • Fail-closed from detected violation to return: < 5s (NFR-004).
  • Zero writes outside .kittify/doctrine/ (content) and .kittify/charter/ (bookkeeping) (NFR-008 / C-001 / C-002).
  • Zero dangling refs in any committed project DRG layer (NFR-009 / FR-008).
  • Zero network calls from the test suite (NFR-010 / C-003).
  • mypy --strict clean (NFR-007) and ≥ 90% line coverage on orchestration code (NFR-001).

Charter Check

GATE: Must pass before Phase 0 research. Re-checked after Phase 1 design.

Charter present at /Users/robert/spec-kitty-dev/charter2/spec-kitty/.kittify/charter/charter.md.

Directive / TacticApplicabilityStatus
DIRECTIVE_003 — Decision DocumentationADR coverage is material for this mission's load-bearing choices.PASS (with action): one ADR will be drafted during Phase 1 covering the adapter seam + atomicity model (see Phase 1 output). Module ownership (Q1), atomicity (Q2), and adapter shape (Q3) decisions are captured here in §"Key Decisions" with rationale and rejected alternatives.
DIRECTIVE_010 — Specification FidelityPlan must not contradict spec.PASS: no deviation from 20 FRs, 10 NFRs, 12 Cs; plan only resolves HOW, not WHAT.
adr-drafting-workflowModel-selection boundary + atomicity mechanism qualify.PASS: ADR drafting scheduled as part of WP3.1 entry gate (see §"Phase 1 Outputs").
problem-decompositionAlready applied — mission decomposed into five coherent WPs with explicit deferrals for WP3.3/3.4/3.5.PASS.
premortem-risk-identificationR-1…R-6 in spec §11 plus premortem in §"Risks & Premortem" below.PASS.
requirements-validation-workflowEvery FR traces to at least one US/SC/EC; every NFR carries a threshold.PASS (verified via spec checklist).
review-intent-and-risk-firstDownstream review order captured in §"Review & Validation Strategy".PASS.
stakeholder-alignmentOperator, downstream agent, reviewer/mission owner are the stakeholder set; surface interactions are explicit via user scenarios US-1…US-8.PASS.
eisenhower-prioritisationWP order (3.1 → 3.2 → 3.6 / 3.7 in parallel → 3.8) reflects importance × unblocking impact.PASS.
Policy — 90% coverageApplies to new orchestration code.PASS (NFR-001).
Policy — mypy --strictApplies to all new modules.PASS (NFR-007).
Policy — integration tests for CLI commandssynthesize and resynthesize --topic both land as CLI commands.PASS: integration tests specified in §"Review & Validation Strategy".

No violations. No Complexity Tracking entries required.

Key Decisions (locked in planning interrogation)

KD-1 · Module ownership — single-owner with thin DRG delegation

All synthesizer code lives under src/charter/synthesizer/. src/doctrine/drg/ remains the stable primitives layer — models, loader, validator, query, merge_layers — and gains no new code in this mission. The charter-side project_drg.py is a thin composer that calls existing doctrine primitives; if it starts inventing reusable DRG write semantics, that is the signal to extract later, not now.

Rationale: keeps Phase 3 coherent as a charter feature; correct dependency direction (charterdoctrine/drg); avoids premature fragmentation across two ownership trees; leaves a clean extraction path later.

Rejected alternatives:

  • (A) single-owner, no delegation boundary — allowed generic graph logic to leak into charter; bad for eventual extraction.
  • (B) cross-package split with DRG write code under src/doctrine/drg/project_layer.py — forced doctrine to change in lockstep with a charter-only feature; rejected as premature.

KD-2 · Atomicity — stage + ordered promote, manifest-last commit

Every synthesis run stages all writes (content and bookkeeping) under a single root at .kittify/charter/.staging/<runid>/, with the promote step demultiplexing into the final locations: content files land under .kittify/doctrine/, bookkeeping files under .kittify/charter/. Full validation runs against the staged tree; only on pass does the pipeline promote via ordered os.replace calls, writing the final synthesis manifest (at .kittify/charter/synthesis-manifest.yaml) last. The synthesis manifest is the authoritative "this bundle is valid and committed" marker. Readers that encounter a missing or internally inconsistent manifest treat the synthesized content as partial-and-rerunable, not authoritative.

Constraint: the live tree is never read as authoritative unless the manifest is present and internally consistent with the promoted files (manifest lists artifact URNs + content hashes; reader verifies hash-match before trusting).

Staging-dir disposition:

  • Success → staging dir wiped.
  • Failure (validation, adapter error, SIGKILL, disk full) → staging dir preserved with a .failed sibling marker for operator diagnosis.

Rejected alternatives:

  • Pure in-memory stage — no recovery story for partial writes on SIGKILL or disk-full.
  • Journal + replay — correct but overkill for kilobyte-scale writes; dragged complexity into Phase 3 without buying anything over (B).

KD-3 · Adapter interface — sync, one-shot + optional batch, override-first provenance identity

Minimum contract:

class SynthesisAdapter(Protocol):
    id: str
    version: str
    def generate(self, request: SynthesisRequest) -> AdapterOutput: ...
    # Optional; orchestration detects presence at runtime.
    def generate_batch(self, requests: Sequence[SynthesisRequest]) -> Sequence[AdapterOutput]: ...

Orchestration is synchronous. When generate_batch is present on an adapter, orchestration uses it; otherwise it falls back to sequential generate calls. No asyncio introduction in this tranche.

AdapterOutput carries optional adapter_id_override and adapter_version_override so long-lived adapters that rotate models can stamp per-call identity. Orchestration records whichever identity was effective for that call.

KD-4 · Fixture-adapter keying — <kind>/<slug>/<blake3-short>.yaml, normalized inputs

Fixtures live at tests/charter/fixtures/synthesizer/<kind>/<slug>/<blake3-short>.yaml. The hash is computed over the normalized SynthesisRequest — fields in canonical order, stable JSON serialization, no mutable ordering — so semantically identical requests do not fragment fixtures.

The fixture adapter computes the expected path for each incoming request and fails loudly with the expected path when the fixture is missing: FixtureAdapterMissingError(expected_path=..., kind=..., slug=..., inputs_hash=...). This makes fixture authoring ergonomic: operator runs once, captures output, checks it in at the exact printed path.

KD-5 · Fail-closed path-guard integrated at write seam

The write seam (PathGuard class) wraps every filesystem mutation path inside the synthesizer. Any attempted write under src/doctrine/ raises PathGuardViolation before the filesystem is touched. The guard is testable at the unit level — orchestration does not gain "smart" write logic; it goes through the guard.

KD-6 · ADR set scheduled for WP3.1

Two ADRs are required before WP3.1 can merge (DIRECTIVE_003):

  • ADR-<date>-1: Charter Synthesizer — Adapter Seam and Provenance Identity — documents KD-3, KD-4 and the narrow seam rationale.
  • ADR-<date>-2: Charter Synthesizer — Atomicity via Stage + Ordered Promote + Manifest-Last Commit — documents KD-2, including rejected alternatives and recovery semantics.

Both ADRs follow the existing architecture/adrs/ naming convention. ADR-6 (synthesizer model selection, #521) is referenced but not authored by this mission.

Project Structure

Documentation (this feature)

kitty-specs/phase-3-charter-synthesizer-pipeline-01KPE222/
├── spec.md                 # /spec-kitty.specify output (complete)
├── plan.md                 # This file (/spec-kitty.plan output)
├── research.md             # Phase 0 output (this command)
├── data-model.md           # Phase 1 output (this command)
├── quickstart.md           # Phase 1 output (this command)
├── contracts/              # Phase 1 output (this command)
│   ├── adapter.py          # SynthesisAdapter Protocol + AdapterOutput dataclass shape
│   ├── provenance.schema.yaml  # Provenance record schema (documented, not code)
│   ├── synthesis-manifest.schema.yaml  # Manifest schema (commit marker)
│   └── topic-selector.md   # Structured selector grammar + error-shape contract
├── checklists/
│   └── requirements.md     # Spec quality checklist (complete, all pass)
└── tasks.md                # (NOT created here — /spec-kitty.tasks)

Source Code Layout (new + modified)

New packagesrc/charter/synthesizer/:

src/charter/synthesizer/
├── __init__.py             # Public API re-exports (orchestrator.synthesize, resynthesize, errors)
├── orchestrator.py         # Top-level entry points: synthesize() + resynthesize(topic)
├── request.py              # SynthesisRequest, SynthesisTarget dataclasses + normalize()
├── adapter.py              # SynthesisAdapter Protocol + AdapterOutput dataclass
├── fixture_adapter.py      # FixtureAdapter + FixtureAdapterMissingError
│                           #   (lives in src/ not tests/ so integration tests can import it,
                            #    but it is only wired in test entrypoints — never in production CLI path)
├── interview_mapping.py    # Interview answer → list[SynthesisTarget] mapping
├── targets.py              # target selection + ordering
├── provenance.py           # Provenance dataclass + writer + reader
├── project_drg.py          # THIN composer over src/doctrine/drg primitives
│                           #   (emit_project_layer, validate_merged, persist)
├── topic_resolver.py       # TopicSelector parsing + 3-tier resolution
├── staging.py              # Staging dir lifecycle + ordered-promote
├── manifest.py             # SynthesisManifest schema + write/read
├── path_guard.py           # PathGuard + PathGuardViolation
└── errors.py               # Structured error taxonomy (synthesis-scoped)

Modified existing files:

src/charter/compiler.py          # FR-009: extend _default_doctrine_service project-root candidate list
                                 #   to include .kittify/doctrine/ (in addition to repo_root/src/doctrine
                                 #   and repo_root/doctrine). Discovery is conditional on directory presence.
src/charter/context.py           # FR-009 / FR-018: extend _build_doctrine_service with the same candidate.
src/charter/_drg_helpers.py      # Already reads .kittify/doctrine/graph.yaml for the project DRG layer;
                                 #   no change required. Listed here as a touchpoint for reviewers, not a diff.
src/charter/bundle.py            # FR-015: manifest-v1 gains optional synthesis-bridging fields that cross
                                 #   the .kittify/charter/ ↔ .kittify/doctrine/ boundary (backwards-compatible).
src/specify_cli/cli/commands/charter.py  # FR-010 / FR-011: add `synthesize` and `resynthesize` subcommands

DO NOT TOUCH (constraint):

src/doctrine/**       # read-only at runtime; KD-1 preserves stable primitives

Tests:

tests/charter/synthesizer/
├── __init__.py
├── conftest.py                              # shared fixtures (doctrine snapshot, drg snapshot)
├── test_orchestrator_synthesize.py          # US-1, US-8; FR-001/002/005/006/014
├── test_orchestrator_resynthesize.py        # US-2, US-3, US-4; FR-011/012/017
├── test_adapter_contract.py                 # FR-003
├── test_fixture_adapter.py                  # KD-4; FixtureAdapterMissingError diagnostics
├── test_interview_mapping.py                # interview-section → target mapping (plus A-3 sentinel)
├── test_provenance.py                       # FR-006, NFR-006 (byte-reproducibility)
├── test_project_drg.py                      # FR-007 / FR-020; additive-only invariants
├── test_topic_resolver.py                   # FR-012 / FR-013; ambiguous + unresolvable + zero-match
├── test_staging_atomicity.py                # KD-2; US-5 fail-closed; EC-5 interrupted run
├── test_manifest.py                         # manifest-last commit marker, hash verification
├── test_path_guard.py                       # FR-016 / NFR-008 / US-7
├── test_validation_gate.py                  # FR-008 / NFR-009; dangling-ref / duplicate / cycle
├── test_schema_conformance.py               # FR-019 / NFR-005
├── test_context_reflects_synthesis.py       # FR-018 / SC-005 (end-to-end observable difference)
├── test_bundle_validate_extension.py        # FR-015
├── test_charter_compile_project_root.py     # FR-009
├── test_performance_envelopes.py            # NFR-002/003/004
└── fixtures/
    └── synthesizer/
        ├── directive/<slug>/<hash>.directive.yaml   # per-target fixtures (extension matches
        ├── tactic/<slug>/<hash>.tactic.yaml         #   shipped repository globs so fixtures
        └── styleguide/<slug>/<hash>.styleguide.yaml #   round-trip through the same loaders)

tests/agent/cli/commands/
├── test_charter_synthesize_cli.py           # CLI integration: synthesize
└── test_charter_resynthesize_cli.py         # CLI integration: resynthesize --topic

Structure Decision: Single-project extension. All new code lives under src/charter/synthesizer/ as a self-contained subpackage. All tests live under tests/charter/synthesizer/ mirroring the module layout. Cross-package edits are limited to extending a candidate list in src/charter/compiler.py and src/charter/context.py (FR-009); src/doctrine/** is strictly read-only.

Storage Decision (Path D): Synthesized artifact content (directives, tactics, styleguides, project DRG graph) is written under .kittify/doctrine/ using filenames that match the existing repository globs (.directive.yaml, .tactic.yaml, *.styleguide.yaml, plus graph.yaml). This is the tree the existing DirectiveRepository, TacticRepository, StyleguideRepository, and _drg_helpers project-layer loader already recognise — no consumer-side reshape is required beyond extending the project-root candidate list. Synthesis bookkeeping (per-artifact provenance sidecars at .kittify/charter/provenance/, commit-marker manifest at .kittify/charter/synthesis-manifest.yaml, ephemeral staging at .kittify/charter/.staging/<runid>/) is kept separate so bundle validate can bridge the two trees without doctrine loaders seeing bundle artifacts.

Directive ID scheme (provisional, tranche-1 default): Synthesized directives use PROJECT_<NNN> IDs (matches Directive.id regex ^[A-Z][A-Z0-9_-]*$; disjoint from shipped DIRECTIVE_<NNN>). The scheme is not locked — if the WP3.2 implementation finds semantic IDs produce cleaner provenance, the regex accepts alternatives without a plan change.

Phase 0: Research

See research.md for the consolidated findings. All questions below are resolved there — no outstanding [NEEDS CLARIFICATION] markers remain.

Questions resolved in Phase 0: 1. R-0-1: What interview-answer surfaces drive which artifact kinds? (Interview → target mapping table) 2. R-0-2: Where does the project DRG layer live on disk? (Canonical path decision) 3. R-0-3: Provenance storage format — single file or per-artifact sidecar? (Format decision) 4. R-0-4: Bundle manifest extension — v1.0.0 additive vs v1.1.0 bump? (Extension strategy) 5. R-0-5: Production-adapter fallback — what happens when no production adapter is configured? (Fallback semantics) 6. R-0-6: Minimum content-hash hygiene — which blake3 variant, how short is "short"? (Hash spec) 7. R-0-7: Existing DRG validator error shape — does it already give us enough to surface structured errors? (Validator reuse)

Phase 1: Design & Contracts

Generated Phase 1 artifacts:

  • data-model.md — concrete entity shapes (SynthesisRequest, SynthesisTarget, AdapterOutput, ProvenanceEntry, SynthesisManifest, ProjectDRGOverlay, TopicSelector, error taxonomy), field-by-field, with validation rules and invariants.
  • contracts/adapter.pySynthesisAdapter Protocol + AdapterOutput dataclass. This is the frozen seam contract; changes require an ADR amendment.
  • contracts/provenance.schema.yaml — YAML schema for provenance entries.
  • contracts/synthesis-manifest.schema.yaml — YAML schema for the commit-marker manifest.
  • contracts/topic-selector.md — structured-selector grammar + error-shape contract.
  • quickstart.md — operator quickstart for a fresh-project synthesis + a targeted resynthesis.

Post-Phase-1 Charter Re-Check: re-ran §"Charter Check" after Phase 1 artifacts existed. No new violations surfaced. Adapter seam and atomicity model are now concrete enough that the scheduled ADRs (ADR-<date>-1, ADR-<date>-2) have authored-ready material — those ADRs will be drafted inside WP3.1 before the WP closes.

Review & Validation Strategy

Validation is layered so the adapter's non-determinism never contaminates the deterministic surface.

LayerCoversHow
Adapter seamFR-003, FR-004, KD-3Protocol conformance tests; fixture adapter round-trip.
Fixture keyingKD-4Hash-stability tests; missing-fixture diagnostic tests.
Interview mappingFR-001, FR-002Table-driven; one test per interview section → target-kind pair.
ProvenanceFR-006, NFR-006Byte-reproducibility under fixed inputs; round-trip read-back.
Staging / atomicityKD-2, FR-008, EC-5Inject failures at stage / validation / promote; assert atomic rollback; assert manifest-last semantics; simulate SIGKILL via partial-write fixtures.
Path guardFR-016, NFR-008, US-7Table-driven negative tests over every write site in the synthesizer.
Project DRGFR-007, FR-008, FR-020, NFR-009Accept / reject-dangling / reject-duplicate / reject-cycle cases; additive-only assertion via shipped-graph snapshot diffing.
Topic resolverFR-012, FR-013, US-6, SC-008One test per tier × (hit / miss / ambiguous / zero-match) — 12 cells minimum.
Schema conformanceFR-019, NFR-005Adapter produces invalid body → orchestration rejects before provenance write.
Context observabilityFR-018, SC-005Run full synthesis → invoke charter context --action specify → assert at least one project-specific item present.
Bundle validateFR-015Regression fixtures for valid / orphan-artifact / orphan-provenance / schema-invalid bundles.
CLI integrationFR-010, FR-011typer CLI runners for synthesize and resynthesize --topic with subprocess-level assertions.
Performance envelopesNFR-002, NFR-003, NFR-004CI-tolerant timing assertions using fixture adapter; thresholds at spec values.
DeterminismFR-014, NFR-006Identical-inputs run produces byte-identical provenance + artifact hashes.

Review order for each WP follows review-intent-and-risk-first: intent summary → invariants touched → risk surface → code changes → tests.

Work Package Breakdown

Concrete plan-level view. Final WP files are authored by /spec-kitty.tasks; this table is the scope anchor + dependency contract.

WPTitleScopeDepends onParallelisable with
WP3.1Synthesizer skeleton + provider seam + path guardsrc/charter/synthesizer/{adapter,fixture_adapter,path_guard,request,errors}.py; orchestrator.py skeleton with write stubs; both ADRs drafted; fixture-adapter diagnostics landed; test harness scaffolded.none
WP3.2Interview-driven synthesis pathinterview_mapping.py, targets.py; orchestrator.synthesize() populated end-to-end but writes are still stubbed; adapter calls real; provenance objects assembled in memory.WP3.1
WP3.6Project-local artifact storage + provenance writerprovenance.py, staging.py, manifest.py; staging→promote→manifest-last pipeline; filesystem layout under .kittify/charter/; bundle.py extension for FR-015; charter bundle validate updates.WP3.2 (artifact body contract)WP3.7
WP3.7Project DRG layer writer + no-dangling-ref enforcementproject_drg.py (thin composer over src/doctrine/drg); validation-gate wiring before staging promote; additive-only enforcement; DoctrineService project_root activation in compiler.py/context.py.WP3.2 (artifact → URN mapping)WP3.6
WP3.8spec-kitty charter resynthesize --topic <selector>topic_resolver.py; CLI command resynthesize; bounded-recomputation orchestrator path; diagnostic UX for ambiguous / zero-match / unresolvable selectors.WP3.6 + WP3.7 durable

Sequencing invariant: WP3.1 must merge before WP3.2/3.6/3.7/3.8 begin. WP3.6 and WP3.7 run in parallel once WP3.2's artifact-body contract is frozen. WP3.8 depends on WP3.6+WP3.7 committed.

Execution-lane hint (for /spec-kitty.tasks): two natural lanes — Lane A: WP3.1 → WP3.2 → WP3.6 → WP3.8; Lane B: WP3.7. Lane B rejoins the main sequence at the WP3.6/3.7 sync point before WP3.8 begins. Final lane assignment is the tasks phase's job; this is guidance, not prescription.

Risks & Premortem

Applying premortem-risk-identification — "imagine this mission failed in six months; why?"

RiskPremortem scenarioMitigation in this plan
R-1 · Seam leak"The adapter interface turned out to carry prompt-engineering logic, so production + fixture adapters diverged in non-obvious ways and CI passed while production regressed."KD-3 freezes generate / generate_batch as the sole entry points; prompt shaping lives inside each adapter implementation. Contracts file contracts/adapter.py is the frozen source of truth.
R-2 · DoctrineService candidate-list ripple"Extending the project-root candidate list surfaced .kittify/doctrine/ as a project layer for projects that never ran charter synthesize, and the empty directory caused surprising downstream behaviour."FR-009 discovery is conditional on directory presence: if .kittify/doctrine/ does not exist, the candidate is skipped and DoctrineService falls back to the existing src/doctrine / doctrine candidates exactly as today. Legacy projects see byte-identical behaviour until they run charter synthesize. test_charter_compile_project_root.py locks this: (a) no .kittify/doctrine/ → same project_root as 3.x today; (b) .kittify/doctrine/ present → project_root points there; (c) empty .kittify/doctrine/ → project_root points there but repositories resolve to empty overlays with no shipped-layer impact.
R-3 · Bundle manifest drift"Bundle v1.0.0 consumers broke because the synthesized block was interpreted as a schema bump."R-0-4 resolves to additive-only extension. bundle.py changes preserve v1.0.0 schema_version; new fields are optional. Regression fixtures in test_bundle_validate_extension.py pin backwards-compat.
R-4 · Provenance brittle under model churn"Adapter id rotated in production and every provenance entry became untrustworthy."KD-3 makes adapter id + version first-class provenance fields with per-call override; invalidation is observable.
R-5 · Topic-selector ambiguity"Operators pushed back on structured-only selectors and we added free-text support without the right error UX."C-004 (free-text rejection) + FR-013 (structured error enumerating candidates) make the affordance teachable; SC-008 gives a testable threshold.
R-6 · ADR-6 dependency creep"WP3.1 stalled waiting for ADR-6 to resolve model policy, blocking the whole tranche."C-006 + KD-6: this mission ships with a pinned default adapter and fixture adapter. ADR-6 resolves the production policy, not the architecture.
R-7 · Staging-dir accumulation"Failed runs left .staging-* dirs behind and eventually filled .kittify/charter/."KD-2 preserves failed staging with a .failed marker; add a charter doctor extension (out of scope for this mission but flagged) OR a staging-dir size warning in bundle validate. Noted in research.md §R-0-8 as follow-up. Staging stays under .kittify/charter/ so doctrine consumers never traverse it.
R-8 · Normalization drift in fixture keying"Two semantically identical interview answers produced different fixture hashes because JSON ordering drifted across dependency upgrades."KD-4 pins normalization: sorted keys + canonical JSON + stable int/float repr. test_fixture_adapter.py asserts normalization invariance under key-order permutations.
R-9 · Silent project-layer DRG reads during interview"Interview-time resolver accidentally picked up a stale project DRG from a previous run and skewed interview defaults."interview.py explicitly uses shipped-only DRG for interview-time resolution (no project layer merge during interview). Test lock in test_interview_mapping.py.
R-10 · Path-guard false-sense-of-security"Path guard existed but some code path bypassed it via Path.write_text direct."PathGuard wraps every write seam; orchestration uses only guard methods. Static-check (grep-level lint test) in test_path_guard.py asserts no direct open(..., 'w') / Path.write_text / shutil.move outside the guard module.

File Clusters (orientation for reviewers)

  • Charter package: src/charter/synthesizer/* (new), src/charter/compiler.py, src/charter/context.py, src/charter/bundle.py (modified — small diffs; compiler.py / context.py edits are candidate-list extensions only). src/charter/_drg_helpers.py is a read touchpoint (already resolves project DRG from .kittify/doctrine/graph.yaml) but requires no diff.
  • CLI: src/specify_cli/cli/commands/charter.py (two new Typer subcommands).
  • Doctrine (unchanged): src/doctrine/drg/** is consumed only; do not expect diffs here.
  • Tests: tests/charter/synthesizer/ (new), tests/charter/fixtures/synthesizer/ (new), tests/agent/cli/commands/test_charter_synthesize_cli.py + test_charter_resynthesize_cli.py (new).
  • ADRs: architecture/adrs/2026-04-XX-1-charter-synthesizer-adapter-seam.md, architecture/adrs/2026-04-XX-2-charter-synthesizer-atomicity.md (new, authored in WP3.1).
  • Migrations / upgrade: none in this tranche (deliberately — ADR-7 is out of scope).

Complexity Tracking

Not applicable. Charter Check passes cleanly with no violations. No justifications required.

Branch Contract (repeat — mandatory)

  • Current branch at workflow start: main
  • Planning / base branch: main
  • Final merge target for completed changes: main
  • branch_matches_target: true
  • Summary: Current branch at workflow start: main. Planning/base branch for this feature: main. Completed changes must merge into main.

Next Step

This command is complete after Phase 1 artifacts land. The next step is /spec-kitty.tasks — the user must invoke it explicitly. Do not proceed to task generation from here.