Quickstart: Phase 1 Excision Developer Workflow
Mission: excise-doctrine-curation-and-inline-references-01KP54J6 Plan: plan.md Spec: spec.md Date: 2026-04-14
This quickstart is for the implementer(s) working through the three WPs. It assumes you have the spec and plan open in another pane.
Prerequisites
main@ the commit this plan was generated against (post #609)pipx install --force --pip-args="--pre" spec-kitty-cliup-to-datepython -m venv .venv && source .venv/bin/activate && uv pip install -e .mypy,ruff,pytestrunnable locallySPEC_KITTY_ENABLE_SAAS_SYNC=1if you need to exercise SaaS sync paths (not required for this mission)
Branch contract
- Start branch:
main - All three WP PRs open against
main - All three WP PRs merge back to
main - Mission
mission_id:01KP54J6W03W8B05F3P2RDPS8S(mid8:01KP54J6) - Mission slug:
excise-doctrine-curation-and-inline-references-01KP54J6 - Always pass
--mission excise-doctrine-curation-and-inline-references-01KP54J6(or--mission 01KP54J6) to anyspec-kittycommand in this repo — there are multiple active missions.
Before starting any WP
1. Pull main and rebase your working branch. 2. Run spec-kitty agent tasks status --mission excise-doctrine-curation-and-inline-references-01KP54J6 to confirm WP lanes. 3. Read the relevant WP slice of plan.md and the full spec.md. 4. Read the contract files relevant to your WP:
- WP1.1:
contracts/occurrence-artifact.schema.yaml,contracts/removed-cli-surface.md - WP1.2:
contracts/occurrence-artifact.schema.yaml - WP1.3:
contracts/occurrence-artifact.schema.yaml,contracts/validator-rejection-error.schema.json,contracts/resolve-transitive-refs.contract.md
WP1.1 — Excise curation surface
Step 1. Author the occurrence artifact FIRST (before any deletion)
# From repo root
mkdir -p kitty-specs/excise-doctrine-curation-and-inline-references-01KP54J6/occurrences
Populate kitty-specs/.../occurrences/WP1.1.yaml per the schema. The to_change list must include every file to be deleted/modified, classified by the eight #393 categories. Reference contracts/occurrence-artifact.schema.yaml.
Step 2. Write the verifier
Add scripts/verify_occurrences.py — a small Python script that:
- Loads an occurrence artifact YAML
- For each category, greps the repo for each
strings[i]withininclude_globsexcludingexclude_globs - Fails if any hit is found that is not listed in
permitted_exceptions - Fails if
requires_mergedcontains WP IDs not yet merged (check viagit log mainor equivalent) - Emits a green/red summary
Run it against WP1.1.yaml before any deletions — it should list every hit you plan to delete.
Step 3. Delete in order
rm -rf src/doctrine/curation/
rm -f src/specify_cli/cli/commands/doctrine.py
rm -f src/specify_cli/validators/doctrine_curation.py
rm -rf tests/doctrine/curation/
rm -f tests/cross_cutting/test_doctrine_curation_unit.py
# Delete _proposed/ trees (only the dirs themselves)
for kind in directives tactics procedures styleguides toolguides paradigms; do
rm -rf "src/doctrine/${kind}/_proposed"
done
Step 4. Unregister the Typer app
Edit src/specify_cli/cli/commands/__init__.py — remove the single app.add_typer(doctrine_module.app, name="doctrine") line and the from . import doctrine as doctrine_module import above it.
Step 5. Update SOURCE templates
Grep SOURCE prose for the removed command names (NOT agent copy dirs):
# SOURCE prose only — agent copies regenerate on upgrade
grep -rn "doctrine curate\|doctrine promote\|doctrine reset\|doctrine status\|_proposed" \
src/specify_cli/missions src/specify_cli/skills src/doctrine \
| grep -v 'curation/imports'
Rewrite any hits in SOURCE templates. Do not touch .claude/, .amazonq/, .augment/, .codex/, .cursor/, .gemini/, .github/prompts/, .kilocode/, .opencode/, .qwen/, .roo/, .windsurf/.
Step 6. Add the regression test
Create tests/specify_cli/cli/test_doctrine_cli_removed.py per contracts/removed-cli-surface.md.
Step 7. Run gates locally
pytest tests/
mypy --strict src/
python scripts/verify_occurrences.py kitty-specs/excise-doctrine-curation-and-inline-references-01KP54J6/occurrences/WP1.1.yaml
All three must be green.
Step 8. Open PR
Target main. PR body should reference #476 and paste the verifier's green summary.
WP1.2 — Strip inline reference fields from shipped artifacts, schemas, models
Only start after WP1.1 is merged to main.
Step 1. Run R-1 audit
Create scripts/r1_inline_vs_graph_audit.py (ephemeral — deleted at end of WP1.2):
- Walks every shipped YAML under
src/doctrine/(exclude_proposed/— already gone — andschemas/,graph.yaml) - Extracts every
tactic_refs: [...]reference - Loads
src/doctrine/graph.yaml - Asserts every inline reference has a matching edge
{from, to, kind: uses} - Emits a
missing_edges.yamlin the mission directory (ephemeral)
Read research.md R-1 for method and expected output shape.
Step 2. Patch graph.yaml with missing edges (if any)
For every missing edge, add a record to src/doctrine/graph.yaml. Preserve file ordering conventions. Add a comment if the origin of the relationship is ambiguous.
Step 3. Author the occurrence artifact
Populate kitty-specs/.../occurrences/WP1.2.yaml per the schema. Categories to cover: yaml_key, symbol_name (for Pydantic fields), docstring_or_comment (for schema comments).
Step 4. Strip inline refs from shipped YAMLs
Edit each of the 13 shipped YAMLs (8 directives, 3 paradigms, 2 procedures) to remove tactic_refs:. For procedures, remove tactic_refs: from each steps[*] entry too.
Use ruamel.yaml round-trip to preserve comments and key order where possible.
Step 5. Strip inline ref fields from schemas and models
# Remove tactic_refs / paradigm_refs / applies_to from these:
src/doctrine/schemas/directive.schema.yaml
src/doctrine/schemas/paradigm.schema.yaml
src/doctrine/schemas/procedure.schema.yaml
# And from per-kind Pydantic models
src/doctrine/directives/models.py
src/doctrine/paradigms/models.py
src/doctrine/procedures/models.py
src/doctrine/tactics/models.py # paradigm_refs if present
src/doctrine/styleguides/models.py # tactic_refs if present
src/doctrine/toolguides/models.py # tactic_refs if present
src/doctrine/agent_profiles/models.py # any of three if present
# And from charter schemas
src/charter/schemas.py # strip applies_to: list[str] from Directive
Step 6. Update tests that asserted these fields
Edit every affected test to assert the fields are absent instead. Do NOT delete these tests.
Step 7. Run gates locally
pytest tests/
mypy --strict src/
python scripts/verify_occurrences.py kitty-specs/excise-doctrine-curation-and-inline-references-01KP54J6/occurrences/WP1.2.yaml
grep -R "tactic_refs\|paradigm_refs\|applies_to" src/doctrine src/charter/schemas.py
Last command must return zero hits outside index.yaml permitted exceptions.
Step 8. Delete the R-1 audit script
rm scripts/r1_inline_vs_graph_audit.py. Commit.
Step 9. Open PR
Target main. PR body references #477 and paste verifier + grep output.
WP1.3 — Validators reject, single context builder, reference-resolver excision
Only start after WP1.2 is merged to main.
Follow the sequencing from plan.md D-1 strictly:
Step 1. Add resolve_transitive_refs() in DRG
Implement per contracts/resolve-transitive-refs.contract.md. Add tests/doctrine/drg/test_resolve_transitive_refs.py covering the six test dimensions from the contract.
Step 2. Flip resolver/compiler imports
Both src/charter/resolver.py and src/charter/compiler.py (and their specify_cli/charter/* twins) swap their resolve_references_transitively import for resolve_transitive_refs. Also load/merge/validate the DRG at the call site (or via a shared helper).
Step 3. Add validator rejection
Edit the seven per-kind validation.py files to reject tactic_refs, paradigm_refs, applies_to with InlineReferenceRejectedError per contracts/validator-rejection-error.schema.json. Add tests/doctrine/test_inline_ref_rejection.py with one negative fixture per kind.
Step 4. Flip build_charter_context call sites to build_context_v2
Update the five call sites:
src/specify_cli/next/prompt_builder.pysrc/specify_cli/cli/commands/charter.pysrc/specify_cli/cli/commands/agent/workflow.pysrc/charter/__init__.py(re-export)src/specify_cli/charter/__init__.py(re-export)src/specify_cli/charter/context.py(wrapper — delete if no longer needed)
Run full pytest at this point. All tests should pass (legacy build_charter_context is now dead code in src/ but still tested).
Step 5. Rename build_context_v2 → build_charter_context
Edit src/charter/context.py:
- Delete the legacy
build_charter_context()function (lines ~33–119) - Rename
build_context_v2(lines ~495+) tobuild_charter_context - Update module docstring/comments
Re-point the call sites back to the now-restored name.
Update src/charter/__init__.py and src/specify_cli/charter/__init__.py re-export lists.
Step 6. Delete reference_resolver.py
rm src/charter/reference_resolver.py
All importers have been flipped in step 2. Verify with:
grep -R "reference_resolver\|resolve_references_transitively\|ResolvedReferenceGraph" src/ tests/
Step 7. Remove include_proposed from catalog
Edit src/charter/catalog.py :: load_doctrine_catalog() — remove the parameter. Update every caller (should be few; most callers pass False implicitly).
Step 8. Add merged-graph-on-live-path regression test
Create tests/charter/test_merged_graph_on_live_path.py. Mock or spy assert_valid and assert it is called on every bootstrap build_charter_context invocation (FR-016).
Step 9. Rewrite tests/charter/test_context.py
Collapse any v1/v2 split into a single-builder suite. Use the Phase 0 golden fixture outputs as the golden set for NFR-002 byte-identical parity.
Step 10. Delete parity and legacy resolver tests
ONLY AFTER steps 1, 8, 9 are green. Delete:
tests/charter/test_context_parity.pytests/charter/test_reference_resolver.py
Run pytest again. Must stay green.
Step 11. Author the final occurrence artifact + index
Populate kitty-specs/.../occurrences/WP1.3.yaml and finalize kitty-specs/.../occurrences/index.yaml with the full mission-level must-be-zero assertion.
Step 12. Run gates locally
pytest tests/
mypy --strict src/
python scripts/verify_occurrences.py kitty-specs/excise-doctrine-curation-and-inline-references-01KP54J6/occurrences/WP1.3.yaml
python scripts/verify_occurrences.py kitty-specs/excise-doctrine-curation-and-inline-references-01KP54J6/occurrences/index.yaml
# NFR-002 byte-parity check (capture pre-merge baseline first, see below)
diff <(spec-kitty charter context --action specify --json) pre_merge_context.json
# Final must-be-zero grep
grep -RE "curation|_proposed|tactic_refs|paradigm_refs|applies_to|reference_resolver|include_proposed|build_context_v2" src tests \
| grep -vFf permitted_exceptions.txt
Step 13. Measure NFR-001 runtime budget
# On main before WP1.1 merged: pytest --durations=0 > baseline_durations.txt
# On WP1.3 branch: pytest --durations=0 > post_durations.txt
# Compare p50 of total runtime across three CI runs; must stay within 5%.
Step 14. Open PR
Target main. PR body references #475 and paste all verifier/grep/parity/runtime outputs.
Verifier (scripts/verify_occurrences.py)
Sketch — fill in during WP1.1.
#!/usr/bin/env python3
"""Verify a phase-1 excision occurrence-classification artifact.
Loads the artifact YAML, walks each category, greps for each string in scope,
and fails if any hit is not listed in permitted_exceptions.
"""
from __future__ import annotations
import sys
from pathlib import Path
from ruamel.yaml import YAML
REPO_ROOT = Path(__file__).resolve().parents[1]
def load_artifact(path: Path) -> dict:
return YAML(typ="safe").load(path.read_text())
def grep_in_scope(string: str, include_globs: list[str], exclude_globs: list[str]) -> list[tuple[str, int, str]]:
hits = []
for glob in include_globs:
for file_path in REPO_ROOT.glob(glob):
if not file_path.is_file():
continue
if any(file_path.match(ex) for ex in exclude_globs):
continue
try:
content = file_path.read_text()
except (UnicodeDecodeError, OSError):
continue
for lineno, line in enumerate(content.splitlines(), 1):
if string in line:
hits.append((str(file_path.relative_to(REPO_ROOT)), lineno, line.rstrip()))
return hits
def main(artifact_path_str: str) -> int:
artifact = load_artifact(Path(artifact_path_str))
is_index = "wps" in artifact # mission-level index vs per-WP artifact
failures: list[str] = []
if is_index:
# handle mission-level index
for assertion in artifact["must_be_zero"]:
literal = assertion["literal"]
scopes = assertion["scopes"]
excluding = assertion.get("excluding", [])
hits = grep_in_scope(literal, scopes, excluding)
permitted = [e["pattern"] for e in artifact.get("permitted_exceptions", [])]
real_hits = [h for h in hits if not any(p in h[0] or p == h[2] for p in permitted)]
if len(real_hits) != assertion.get("final_count", 0):
failures.append(f"Index assertion violated for literal '{literal}': {len(real_hits)} hits, expected {assertion.get('final_count', 0)}")
for h in real_hits[:5]:
failures.append(f" {h[0]}:{h[1]} {h[2]}")
else:
# handle per-WP artifact
for cat in artifact["categories"]:
for string in cat["strings"]:
hits = grep_in_scope(string, cat["include_globs"], cat["exclude_globs"])
permitted = [e["pattern"] for e in artifact.get("permitted_exceptions", [])]
real_hits = [h for h in hits if not any(p in h[0] or p == h[2] for p in permitted)]
if len(real_hits) != cat["expected_final_count"]:
failures.append(f"{artifact['wp_id']} category '{cat['name']}' string '{string}': {len(real_hits)} hits, expected {cat['expected_final_count']}")
for h in real_hits[:5]:
failures.append(f" {h[0]}:{h[1]} {h[2]}")
if failures:
print("VERIFIER FAILED")
print("\n".join(failures))
return 1
print(f"VERIFIER GREEN for {artifact_path_str}")
return 0
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: verify_occurrences.py <artifact.yaml>")
sys.exit(2)
sys.exit(main(sys.argv[1]))
This is a sketch — final version lives under scripts/ after WP1.1.
Escalation
If any of the following happen, stop and escalate (open a comment on the tracking issue):
- R-1 reveals more than ~5 missing graph edges (suggests the inventory was incomplete and may indicate Phase 0 calibration gaps).
- R-2 behavioral equivalence fails for any shipped directive (means
resolve_transitive_refshas a semantic gap — fix inresolve_transitive_refsbefore proceeding; do NOT patch the legacy resolver). - Any of the existing tests fail in a way that is NOT explained by the known excision set (suggests a hidden dependency — capture in the occurrence artifact and escalate).
- NFR-001 runtime budget is projected to be breached.
- A caller of a removed symbol is discovered outside the known five
build_charter_contextcall sites.
Escalation is a comment on #463 plus a [NEEDS CLARIFICATION: ...] marker on the affected WP's occurrence artifact. Do not silently work around it.