This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
See also:
AGENTS.md(deep architecture map + high-signal rules) andINSTRUCTIONS.md(contributor scope per sub-project).
uv sync
source .venv/bin/activate# All tests (unit/fake, no live server required)
pytest tests/
# Single test file or test function
pytest tests/micro-tests/test_v2.py
pytest tests/micro-tests/test_v2.py::test_name
# Filter by marker (unit | integration | slow | auth | format_sets | mcp)
pytest -m unit
pytest -m integration
# Live Egeria server required
pytest tests/ --live-egeria
# or
PYEG_LIVE_EGERIA=1 pytest tests/asyncio_mode = auto — async test functions need no event-loop boilerplate.
Tests live in three folders under tests/:
micro-tests/— unit/formatter/MCP tests, no live serverfunctional-tests/— per-OMVS testsscenario-tests/— full lifecycle (Create → Link → Delete) tests
Each OMVS module has a corresponding test file in its matching folder.
# Dr. Egeria — process a markdown file
dr_egeria <file> # default: validate
dr_egeria <file> --validate
dr_egeria <file> --process
dr_egeria <file> --process --debug # prints every Egeria API call + body
dr_egeria <file> --process --summary-only # suppress per-command analysis
# Regenerate report specs after adding/changing compact command JSON
refresh_specs # both Basic + Advanced (default)
refresh_specs --usage-level Basic # restrict to a single usage level
# Validate compact command JSON files
validate_compact_specs
# Generate Markdown command template files (for user authoring)
gen_md_cmd_templates # Basic attributes only
gen_md_cmd_templates --advanced # All attributes- Always use
git commit -sto sign off commits. This appendsSigned-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>— DCO is enforced on this repo and unsigned commits will be rejected. - Do not add
Co-Authored-By:lines to commit messages.
pyegeria/ → commands/ (hey_egeria CLI)
pyegeria/ → md_processing/ (Dr. Egeria)
Changes to pyegeria/ (SDK API, config, report formatting) can break both.
pyegeria/core/ — transport, auth, config
_base_platform_client.py→_base_server_client.py→_server_client.py: layered HTTP stackconfig.py: Pydantic-settings config; precedence = explicit args > OS env >.env>config.json> defaultsutils.py: shared helpers (body_slimmer,make_format_set_name_from_type, etc.)
pyegeria/omvs/ — 40+ service-specific clients (one file per OMVS)
- Every public method has an
_async_*implementation and a sync wrapper callingasyncio.get_event_loop().run_until_complete(...). - All public methods decorated with
@dynamic_catch. - Ground truth for API URLs and request bodies is in
pyegeria/http clients/Egeria-api-*.http— check these files before constructing URLs.
pyegeria/egeria_tech_client.py — EgeriaTech facade
- Uses
__getattr__to lazily proxy attribute access across all OMVS subclients; do not eagerly instantiate. create_egeria_bearer_token()/set_bearer_token()propagate tokens to every subclient.
pyegeria/view/ — output formatting and report spec registry
base_report_formats.py:generated_format_setsdict (auto-generated byrefresh_specs); do not hand-edit.output_formatter.py:generate_output()— materializes elements into MD/LIST/DICT/REPORT formats._output_format_models.py: Pydantic modelsColumn/Format/FormatSet— define new specs with these, not raw dicts.- Generated report specs follow
{Type}-DrE-{Basic|Advanced}naming; carry an optionalfamilystring for discovery.
Full pipeline for one process_md_file_v2() call:
Markdown file
↓ UniversalExtractor (extraction.py)
splits on ## headers / horizontal rules → DrECommand objects
↓ setup_dispatcher() (dr_egeria.py)
loads COMMAND_DEFINITIONS from compact JSON specs
registers {command_key → ProcessorClass} in V2Dispatcher
↓ V2Dispatcher.dispatch_batch() (dispatcher.py)
sequential; shared context["planned_elements"] for inter-command GUID resolution
alias resolution → fuzzy verb-stripping → subtype fallbacks
↓ AsyncBaseCommandProcessor.execute() (processors.py)
1. AttributeFirstParser.parse()
2. derive qualified name
3. fetch_as_is (cache → Egeria lookup)
4. CommandRewriter: Create↔Update upsert transitions
5. resolve reference GUIDs for all attributes
6. validate_only() → markdown analysis table
7. apply_changes() ← abstract; implemented per processor
8. render_result_markdown(guid)
↓ dr_egeria.py: assemble final_output, write processed-*.md
Key files:
| File | Role |
|---|---|
dr_egeria.py |
Entry point; setup_dispatcher(); process_md_file_v2() |
v2/extraction.py |
UniversalExtractor → DrECommand dataclass |
v2/dispatcher.py |
V2Dispatcher — routing, alias resolution, fallbacks |
v2/processors.py |
AsyncBaseCommandProcessor — base parse/validate/apply logic |
v2/rewriters.py |
CommandRewriter — Create↔Update auto-transitions |
md_processing_utils/md_processing_constants.py |
COLLECTION_SUBTYPES, PROJECT_SUBTYPES, COMMAND_DEFINITIONS, verb groups |
md_processing_utils/common_md_utils.py |
Body builders (set_element_prop_body, domain helpers) |
data/compact_commands/*.json |
Tinderbox-exported command specs — ground truth for attributes |
Compact command JSON format — three sections per file:
attribute_definitions— full metadata per attribute (style, labels, valid values, etc.)bundles— reusable attribute groups; single inheritance via"inherits"commands— references a bundle + adds custom attributes; expands to a full command spec at load time
These files are Tinderbox exports — never hand-edit. Describe desired changes so the user can make them in Tinderbox and re-export. After any change, run refresh_specs to regenerate base_report_formats.py.
Dispatcher registration — how new commands get wired in:
COLLECTION_SUBTYPESandPROJECT_SUBTYPESdrive automaticCreate/Updaterouting toCollectionManagerProcessor/ProjectProcessor— adding a type to these lists is all that's required.- Families driven entirely by compact spec (Actor Manager, Governance, Solution Architect) use a
register_*_processors()helper loop indr_egeria.py. register_governance_processors()is family-name-gated, not automatic — it only registers commands whose compact-specfamilyis literally"Governance Officer"(or, as of theAction Authorfamily, also"Action Author"). Adding a brand-new family whose commands should reuseGovernanceProcessor/GovernanceLinkProcessormeans adding its name to that check explicitly — nothing is wired up just because the compact JSON exists and validates. WithinAction Author, the fourLinkcommands are routed byOM_TYPE, not by verb alone, across two dedicated processors inmd_processing/v2/action_author.py(neither uses the generic peer-link mechanism, since both need relationship propertiesPeerDefinitionPropertieshas no room for):Link First/Next Process Step(OM_TYPEGovernanceActionProcessFlow/NextGovernanceActionProcessStep) →ActionProcessStepLinkProcessor, callingaction_author.setup_first/next_action_process_stepwithGovernanceActionProcessFlowProperties(guard/requestParameters) /NextGovernanceActionProcessStepProperties(guard/mandatoryGuard). Verified end-to-end against a live server — relationship properties persist correctly.Link Action to Action Executor/Target(OM_TYPEGovernanceActionExecutor/TargetForGovernanceAction) →ActionExecutorTargetLinkProcessor, callingaction_author.link_governance_action_executor/link_target_for_governance_actionwithGovernanceActionExecutorProperties(requestType/requestParameters/requestParameterFilter/requestParameterMap/actionTargetFilter/actionTargetMap) /TargetForGovernanceActionProperties(actionTargetName). Added 2026-07-15 — not yet verified against a live server.
- Everything else is an explicit
reg("Verb Object", ProcessorClass)call insetup_dispatcher().
Body builders — all flow through one function:
set_element_prop_body()incommon_md_utils.pyis the base inner-properties builder for all element types.- Every domain helper (
set_collection_manager_body,set_actor_manager_prop_body,set_gov_prop_body,set_data_field_body) calls it and adds type-specific fields on top. - To add a new
Referenceable-level property, add it once toset_element_prop_body().
Adding a new processor — minimum steps:
- Add the command spec to the relevant compact JSON (user does this in Tinderbox).
- Add the type to
COLLECTION_SUBTYPES/PROJECT_SUBTYPESif it's a collection/project subtype (auto-routes). - Otherwise implement
apply_changes()in a newAsyncBaseCommandProcessorsubclass and register it insetup_dispatcher(). - Run
refresh_specsto generate the result-table format spec.