Map of Functions (MoF)
The MoF is a structured knowledge base that models a system's functions, their responsibilities, relationships, and impact rules, so humans and AI agents can plan and execute changes with safety and context. The central goal: before changing a function, know exactly who depends on it and what can break.
The artifact produced and maintained by this skill is docs/MOF.md at the project root (create docs/ if it doesn't exist). If the project has an instruction file such as CLAUDE.md or AGENTS.md, add a line referencing the MoF as mandatory reading before structural changes — that line, not this description, is what reliably makes the map get consulted.
Don't confuse it with a navigation map (e.g. a docs/MOC.md, Map of Content): the MoF documents technical blast radius between functions; a navigation map documents project navigation. Keep the two decoupled — never merge one's content into the other.
Non-negotiable principles
- Knowledge before documentation. The MoF exists to support reasoning, not to fulfill formality.
- One reason to change, one Responsibility. A
Responsibility is the atomic unit — a single verb-object capability with a single reason to change ("Authorize invoice approval", not "Approve invoice" bundling authorization, notification, and audit logging). A Function groups the Responsibilities that share one code artifact (code_ref); it carries no behavior of its own.
- Incremental discovery. Incomplete knowledge is acceptable; incorrect assumption is not. Mark uncertainty with
status: unverified and never turn an unverified SOLID decision — srp_status, ocp_status, or a live LSP/ISP/DIP read — into a definitive status.
- Never invent business rules. When the code doesn't answer, ask the human. Record the question in
open_questions.
- Traceability. Every item has a unique ID; before creating one, grep the MoF for the prefix and use the next free number — never reuse or guess the next one from memory.
Responsibility (RESP_<DOMAIN>_<NNN>) and Function (F_<DOMAIN>_<NNN>) are domain-scoped so they don't collide across domains mapped in different sessions; other types (R_, IR_, EVT_, W_) use a simple sequential <PREFIX>_<NNN>.
- One fact, one home.
relationships[] is the source of truth for every edge, recorded between Responsibilities. impact_index is a projection of the map, while a Function's role, nature, srp_status/srp_rationale, and ocp_status/ocp_rationale are projections of its Responsibilities and code evidence; regenerate each projection instead of hand-editing it alone. LSP, ISP, and DIP are never projected into a stored status anywhere — they are read live from relationships[], functions[].nature, interfaces[], and cross_cutting[] once those records are already open, so evaluate them fresh on every Query instead of adding a persisted field for them.
- Every section has a reader. Query names the consumer of every part of the template. Anything you add must earn one.
- Living document. Every code change triggers a Map review (see Map § After a change lands).
Operating modes
Identify the mode before acting:
| Situation |
Mode |
| No MoF, an outdated one, or a code change just landed |
Map |
| Task to create, modify, or refactor code in a project with a MoF |
Query |
| User asks to see the MoF as a diagram, or for a SOLID checkup |
Visualize |
Map — discover and maintain
Never build the complete MoF in one pass, whether you're starting from zero or folding in a change that just landed. Follow the cycle: discover → classify → validate → expand → repeat.
Discovery order
- Inventory — session scratch, never written to the file. Walk the repository structure (directory tree, entry points, routes, handlers, workers, migrations, integration configs) and list every concept found — or, if a change just landed, the files it touched. It exists so nothing is dropped before step 3, and is fully superseded once classified. Concepts you cannot classify go to
open_questions — there is no permanent inventory section.
- Domains. Group the inventory into business capabilities. Each Responsibility belongs to exactly one primary domain.
- Responsibilities. For each code artifact, decompose it into its distinct reasons to change: one
Responsibility per side effect, business rule, or concern it carries. Fill the registry (mof-template.md), starting from entry points and descending through the calls. Prioritize Responsibilities with side effects (database writes, events, external calls) — highest risk. A code artifact that only ever does one thing yields exactly one Responsibility; one that does several yields several, all sharing the same code_ref. An abstract artifact (interface, abstract class, protocol, port) carries no side effect and no business rule, but it is not skipped: record one Responsibility per capability its contract declares, whose single reason to change is that contract changing ("Declare the payment charge contract"). Without them an implements/extends edge would have no target to point at, since every edge is recorded between Responsibility ids.
- Functions. Group Responsibilities by shared
code_ref into Function entries. Count only reasons for change implemented by that artifact; a call to another Responsibility does not make the caller carry that Responsibility. Record the artifact's role (orchestrator, executor, mixed, or unverified) and nature (concrete when the artifact has its own implementation, abstract when it is an interface/abstract class/contract with no independent behavior). Compute srp_status as ok when one reason for change is implemented, violation when independent reasons share the artifact, and unverified when the evidence is insufficient, then write the srp_rationale that explains the decision. Compute ocp_status the same way: ok when new variants are added through a relationship into an abstract target (an implements edge, or a calls edge to a nature: abstract Function) without editing code_ref, violation when code_ref itself contains a type-discriminant branch (if/switch on a kind/type field) that has grown or would grow with each new variant, unverified when the evidence is insufficient — then write ocp_rationale.
- Relationships. For each Responsibility, trace in the code what it calls and what calls it. Use reference search, not memory. Record each edge once, in
relationships[], between Responsibility ids, using implements/extends (not calls) when the edge is a subtype or interface conformance. When an edge only exercises part of its target's surface (the caller depends on a Function that groups more Responsibilities than it actually uses), record which ones in consumed_interfaces, as Responsibility ids — this is the raw evidence Query reads live for ISP, not a status to compute now.
- Entities and events. Record which Responsibilities read and which modify each entity, which publish and which consume each event. These are the impact paths the call graph cannot see — Query step 4 depends on them.
- Impact rules. For every Responsibility with 2+ consumers, or with side effects, create an
IR_ rule.
- Cross-cutting rules. Record auth, transactions, error handling, and retries affecting multiple Responsibilities. When a domain boundary constrains dependency direction (e.g. "WORKER may depend on BILLING's published interface, never its concrete classes"), record it as
kind: architecture — this is what a live DIP check is validated against.
- Generate
impact_index last, projected from everything above — it is only correct if built after the rest. Format: mof-template.md.
Validation with the human
At the end of each cycle, present a summary of what was mapped and list the open_questions. Every human answer is incorporated and the item moves from unverified to verified. In large codebases, propose mapping one domain per session; after a landed change, propose reviewing just the Responsibilities and Functions it touched.
Quality criteria per item
Every Responsibility must answer: why it exists; its single reason to change; which domain owns it; which entities it manipulates; which events it consumes and publishes; what breaks if it changes.
Every Function must answer: which Responsibilities it groups, what role and nature the artifact has, and why its srp_status/srp_rationale and ocp_status/ocp_rationale are what they are.
Every Entity must answer: who owns it; which Responsibilities read it; which modify it.
Every Workflow must answer: which Responsibilities compose it; where it starts; where it ends. Workflows never duplicate Responsibility descriptions — they only sequence them by ID.
When to split by domain
Split by domain ownership, not by line count. A domain is a valid split when its Responsibilities have a coherent business capability and a clear owner, even when the file is short. Keep one docs/MOF.md when the project has no stable domain boundary or when splitting would create artificial fragments. When split, docs/mof/<domain>.md holds that domain's Responsibilities, Functions, Entities, Events, and intra-domain Relationships; docs/MOF.md remains the global source for Metadata, the complete impact_index, the domain registry, cross-domain Relationships and Impact Rules, Cross-Cutting Rules, Open Questions, and Revision History.
impact_index never splits. It stays whole in docs/MOF.md covering every domain, so a cross-domain traversal still costs one file read. Never duplicate a Responsibility across two files.
When a split map changes, update the owning domain file and the root docs/MOF.md in the same change. Keep domain-local responsibilities, functions, entities, events, and relationships in their domain file; keep cross-domain edges and global projections in the root file. A Responsibility has one home only, and the root index must be regenerated from all domain files after every structural change. The root domain registry is the authoritative file-to-domain mapping; do not infer ownership from folder names alone.
After a change lands
Update the owning domain file and the root docs/MOF.md when the map is split, or only docs/MOF.md when it is not split:
- Affected Responsibilities (their description, interfaces, side effects, state), and the
role, nature, srp_status/srp_rationale, and ocp_status/ocp_rationale of any affected Function
relationships[] created, changed, or removed
entities[] / events[] where the change altered who reads, writes, publishes, or consumes
- Impact rules the change invalidated or created
impact_index lines for every touched Responsibility, regenerated from the sections above
mof_meta.last_updated and mof_meta.last_commit, and version when the change is structural — Query step 2 uses both commit and timestamp evidence, so leaving either stale makes the map untrusted
- Revision history (version, date, commit, summary)
The MoF is stale when it no longer reflects the system's behavior. Stale documentation is worse than none: it leads the agent to incorrect assumptions.
Query — before changes
Mandatory before proposing any code change that touches logic, contracts, or behavior. Purely cosmetic changes (typos, formatting, comments, doc text) skip straight to the edit.
Read mof_meta and impact_index first. They locate seeds, expose the raw freshness evidence (last_updated and last_commit — there is no stored freshness verdict to trust), and provide the first traversal projection. Then open only the relevant relationships[], entities[], events[], workflows[], impact_rules[], and cross_cutting[] entries needed to complete the radius; do not load the whole docs/MOF.md, and do not open any Responsibility's full block until it is already inside the computed radius. This is the flow, not a size-dependent optimization.
- Locate the seeds. Grep
impact_index for the task's subject: capability name, code_ref path, or domain. A Function id resolves to every Responsibility it groups.
- Check freshness, scoped to the seeds. With the seeds'
code_ref paths known, compare the latest relevant commit with mof_meta.last_commit and compare commit timestamps with mof_meta.last_updated. Classify each seed as fresh when both pieces of evidence are covered, stale when relevant code is newer, or unverified when a path, commit, or timestamp cannot be proved. Run a mini Map cycle only for a stale seed. Never freshness-check the whole repo — a repo-wide git log reports stale after any commit anywhere.
- Traverse the call graph. From each seed, follow
exp: (downstream) to depth 2. Read the relevant relationship records to determine criticality; go deeper along an edge only when it is critical. One exception runs upstream: when a seed belongs to a Function with nature: abstract, also follow its dep: along implements/extends edges — a contract's implementors are upstream of it, and they all break when the contract changes, so exp: alone would report an interface change as affecting nothing. Record the depth reached and the relationship evidence. If the radius swallows most of the map, that is a failed query — say so and narrow the change instead of reporting the whole system as affected.
- Fan out through state — two paths the call graph cannot see:
- Entities. For each in-radius Responsibility writing an entity (
ent: marked (w)), read the relevant entity record and add every Responsibility that reads it. A writer's behavior change breaks its readers with no call edge between them.
- Events. For each in-radius Responsibility publishing an event (
evt: marked +), read the relevant event record and add its consumed_by. Loose coupling still transmits breakage.
- Check artifact quality exposure. Look at
srp: and ocp: on each in-radius line. srp:violation means the Responsibility physically shares a code artifact with siblings that implement independent reasons for change; ocp:violation means the artifact contains a type-discriminant branch a new variant would grow. Either unverified status means that exposure cannot yet be trusted. Pull the Function's siblings into review and report the evidence instead of inferring a violation.
- Check workflows. Any
workflows[] whose sequence contains an in-radius Responsibility needs end-to-end review — a change can be locally correct and still break the flow's contract.
- Apply impact rules. Take the
ir: ids from each in-radius index line and read those rules. Their recommended_actions are required work, not suggestions.
- Apply cross-cutting rules. Check
cross_cutting for rules binding the in-radius Responsibilities. A change that satisfies its Responsibility and violates a cross-cutting rule is a defect. For a kind: architecture rule, also check every in-radius relationship it binds: a cross-domain edge whose target Function has nature: concrete where the rule requires an abstraction is a live DIP violation.
- Only now read the details — for the in-radius Responsibilities and only those:
responsibilities, non_responsibilities, interfaces, side_effects, state, notes. With interfaces open, also resolve LSP and ISP for any in-radius relationship that qualifies: for an implements/extends edge, compare the interfaces.inputs/outputs of from and to — a narrowed precondition, widened postcondition, or a new exception the supertype doesn't declare is a violation; for any edge carrying consumed_interfaces, compare it against the target Function's full responsibilities[] — a small, tightly-coupled subset of a much larger surface is a violation. Report both as live reads, never as a cached status.
- Derive actions: tests to update, contracts to review, documentation and ADRs to create.
- Only then propose the code, respecting each Responsibility's
responsibilities and non_responsibilities.
The Query result must distinguish verified facts from assumptions and report the evidence path for every item added to the radius. If a relationship references a missing Responsibility, entity, event, workflow, or rule, preserve the identifier and mark it unresolved rather than silently dropping it.
If the task touches code absent from the MoF, run a mini Map cycle for it first.
Agent response pattern
Make the plan explicit:
- Responsibilities involved:
RESP_..., grouped under their F_...
- Blast radius: downstream Responsibilities requiring review, and the traversal depth reached
- Reached via state: Responsibilities pulled in by entity or event fan-out, not by a call edge
- Shared-code exposure: sibling Responsibilities pulled in by a
srp_status or ocp_status of violation or unverified, not by any edge
- SOLID decisions:
role, nature, srp_status/srp_rationale, and ocp_status/ocp_rationale for every affected Function; for LSP and ISP, the live read for each qualifying in-radius relationship (or "not applicable" when no implements/extends/consumed_interfaces edge is in radius); for DIP, the architecture rule checked and its verdict
- Workflows affected:
W_...
- Impact rules triggered:
IR_...
- Cross-cutting rules in play: which, and how the change satisfies them
- Evidence status:
verified, unverified, or unresolved for each relationship path
- Intended actions: tests, contracts, documentation
Visualize
Only on explicit user request ("visualize the MoF", "generate the technical report", "check for SOLID violations", "audit the map") — never automatically after Map. Generating the HTML on every code change would add noise and unnecessary work.
Produces docs/MOF.html: a static single-file technical workbench with metadata, global search, Function selection, A/B/C navigation, domains, Functions, Responsibilities, technical relationships, entities, events, workflows, impact rules, shared rules, open questions, a complete footer legend, and print-friendly CSS. A request framed as a checkup or audit gets the same output — the Functions table's role, nature, srp_status/srp_rationale, and ocp_status/ocp_rationale provide the artifact-level SOLID view; the Relationships table's type, consumed_interfaces, and the target's nature give the evidence for a manual LSP/ISP/DIP read. The page shell is a fixed asset (mof-shell.html) that you copy and fill at its markers — never retype it. Fill procedure: visualization.md.
docs/MOF.md document template
Full structure (Metadata, Impact Index, Responsibilities, Functions, Entities, Events, Workflows, Relationships, Impact Rules, Cross-Cutting Rules, Open Questions, Revision History): mof-template.md. Used by Map.
Maturity criterion
The MoF is mature when it lets an agent understand → reason → plan → implement → validate → document a change with minimal additional context, when every Responsibility with side effects has at least one impact rule, when exp: in impact_index is verified against the real code, when every Function's srp_status and ocp_status reflect a deliberate choice rather than an accident of how the code grew, when every implements/extends/consumed_interfaces edge carries enough evidence for a live LSP/ISP read and every cross-domain edge has been checked against an architecture rule for DIP, and when a Query resolves a blast radius without reading a single Responsibility's full block outside that radius.
1---2name: mof3description: Map of Functions (MoF) — a living map of a system's functions, dependencies, and blast radius, consulted before any code change. Use when asked to create, update, or consult a MoF; to assess the blast radius of a change, refactor, or fix; to understand dependencies between functions or modules; to check a codebase for SOLID violations (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion); or to render the MoF as a diagram. Use proactively in any project that already has a docs/MOF.md.4---56# Map of Functions (MoF)78The MoF is a structured knowledge base that models a system's functions, their responsibilities, relationships, and impact rules, so humans and AI agents can plan and execute changes with safety and context. The central goal: before changing a function, know exactly **who depends on it and what can break**.910The artifact produced and maintained by this skill is `docs/MOF.md` at the project root (create `docs/` if it doesn't exist). If the project has an instruction file such as `CLAUDE.md` or `AGENTS.md`, add a line referencing the MoF as mandatory reading before structural changes — that line, not this description, is what reliably makes the map get consulted.1112Don't confuse it with a navigation map (e.g. a `docs/MOC.md`, Map of Content): the MoF documents technical blast radius between functions; a navigation map documents project navigation. Keep the two decoupled — never merge one's content into the other.1314## Non-negotiable principles15161. **Knowledge before documentation.** The MoF exists to support reasoning, not to fulfill formality.172. **One reason to change, one Responsibility.** A `Responsibility` is the atomic unit — a single verb-object capability with a single reason to change ("Authorize invoice approval", not "Approve invoice" bundling authorization, notification, and audit logging). A `Function` groups the Responsibilities that share one code artifact (`code_ref`); it carries no behavior of its own.183. **Incremental discovery.** Incomplete knowledge is acceptable; incorrect assumption is not. Mark uncertainty with `status: unverified` and never turn an unverified SOLID decision — `srp_status`, `ocp_status`, or a live LSP/ISP/DIP read — into a definitive status.194. **Never invent business rules.** When the code doesn't answer, ask the human. Record the question in `open_questions`.205. **Traceability.** Every item has a unique ID; before creating one, grep the MoF for the prefix and use the next free number — never reuse or guess the next one from memory. `Responsibility` (`RESP_<DOMAIN>_<NNN>`) and `Function` (`F_<DOMAIN>_<NNN>`) are domain-scoped so they don't collide across domains mapped in different sessions; other types (`R_`, `IR_`, `EVT_`, `W_`) use a simple sequential `<PREFIX>_<NNN>`.216. **One fact, one home.** `relationships[]` is the source of truth for every edge, recorded between Responsibilities. `impact_index` is a projection of the map, while a Function's `role`, `nature`, `srp_status`/`srp_rationale`, and `ocp_status`/`ocp_rationale` are projections of its Responsibilities and code evidence; regenerate each projection instead of hand-editing it alone. LSP, ISP, and DIP are never projected into a stored status anywhere — they are read live from `relationships[]`, `functions[].nature`, `interfaces[]`, and `cross_cutting[]` once those records are already open, so evaluate them fresh on every Query instead of adding a persisted field for them.227. **Every section has a reader.** Query names the consumer of every part of the template. Anything you add must earn one.238. **Living document.** Every code change triggers a Map review (see Map § After a change lands).2425## Operating modes2627Identify the mode before acting:2829| Situation | Mode |30| ------------------------------------------------------------------ | ------------- |31| No MoF, an outdated one, or a code change just landed | **Map** |32| Task to create, modify, or refactor code in a project with a MoF | **Query** |33| User asks to see the MoF as a diagram, or for a SOLID checkup | **Visualize** |3435---3637## Map — discover and maintain3839Never build the complete MoF in one pass, whether you're starting from zero or folding in a change that just landed. Follow the cycle: **discover → classify → validate → expand → repeat**.4041### Discovery order42431. **Inventory — session scratch, never written to the file.** Walk the repository structure (directory tree, entry points, routes, handlers, workers, migrations, integration configs) and list every concept found — or, if a change just landed, the files it touched. It exists so nothing is dropped before step 3, and is fully superseded once classified. Concepts you cannot classify go to `open_questions` — there is no permanent inventory section.442. **Domains.** Group the inventory into business capabilities. Each Responsibility belongs to exactly one primary domain.453. **Responsibilities.** For each code artifact, decompose it into its distinct reasons to change: one `Responsibility` per side effect, business rule, or concern it carries. Fill the registry ([`mof-template.md`](mof-template.md)), starting from entry points and descending through the calls. Prioritize Responsibilities with side effects (database writes, events, external calls) — highest risk. A code artifact that only ever does one thing yields exactly one Responsibility; one that does several yields several, all sharing the same `code_ref`. An **abstract artifact** (interface, abstract class, protocol, port) carries no side effect and no business rule, but it is not skipped: record one Responsibility per capability its contract declares, whose single reason to change is that contract changing ("Declare the payment charge contract"). Without them an `implements`/`extends` edge would have no target to point at, since every edge is recorded between Responsibility ids.464. **Functions.** Group Responsibilities by shared `code_ref` into `Function` entries. Count only reasons for change implemented by that artifact; a call to another Responsibility does not make the caller carry that Responsibility. Record the artifact's `role` (`orchestrator`, `executor`, `mixed`, or `unverified`) and `nature` (`concrete` when the artifact has its own implementation, `abstract` when it is an interface/abstract class/contract with no independent behavior). Compute `srp_status` as `ok` when one reason for change is implemented, `violation` when independent reasons share the artifact, and `unverified` when the evidence is insufficient, then write the `srp_rationale` that explains the decision. Compute `ocp_status` the same way: `ok` when new variants are added through a relationship into an abstract target (an `implements` edge, or a `calls` edge to a `nature: abstract` Function) without editing `code_ref`, `violation` when `code_ref` itself contains a type-discriminant branch (`if`/`switch` on a kind/type field) that has grown or would grow with each new variant, `unverified` when the evidence is insufficient — then write `ocp_rationale`.475. **Relationships.** For each Responsibility, trace in the code what it calls and what calls it. Use reference search, not memory. Record each edge once, in `relationships[]`, between Responsibility ids, using `implements`/`extends` (not `calls`) when the edge is a subtype or interface conformance. When an edge only exercises part of its target's surface (the caller depends on a Function that groups more Responsibilities than it actually uses), record which ones in `consumed_interfaces`, as Responsibility ids — this is the raw evidence Query reads live for ISP, not a status to compute now.486. **Entities and events.** Record which Responsibilities read and which modify each entity, which publish and which consume each event. These are the impact paths the call graph cannot see — Query step 4 depends on them.497. **Impact rules.** For every Responsibility with 2+ consumers, or with side effects, create an `IR_` rule.508. **Cross-cutting rules.** Record auth, transactions, error handling, and retries affecting multiple Responsibilities. When a domain boundary constrains dependency direction (e.g. "WORKER may depend on BILLING's published interface, never its concrete classes"), record it as `kind: architecture` — this is what a live DIP check is validated against.519. **Generate `impact_index` last**, projected from everything above — it is only correct if built after the rest. Format: [`mof-template.md`](mof-template.md).5253### Validation with the human5455At the end of each cycle, present a summary of what was mapped and list the `open_questions`. Every human answer is incorporated and the item moves from `unverified` to `verified`. In large codebases, propose mapping one domain per session; after a landed change, propose reviewing just the Responsibilities and Functions it touched.5657### Quality criteria per item5859Every **Responsibility** must answer: why it exists; its single reason to change; which domain owns it; which entities it manipulates; which events it consumes and publishes; what breaks if it changes.60Every **Function** must answer: which Responsibilities it groups, what role and `nature` the artifact has, and why its `srp_status`/`srp_rationale` and `ocp_status`/`ocp_rationale` are what they are.61Every **Entity** must answer: who owns it; which Responsibilities read it; which modify it.62Every **Workflow** must answer: which Responsibilities compose it; where it starts; where it ends. Workflows never duplicate Responsibility descriptions — they only sequence them by ID.6364### When to split by domain6566Split by domain ownership, not by line count. A domain is a valid split when its Responsibilities have a coherent business capability and a clear owner, even when the file is short. Keep one `docs/MOF.md` when the project has no stable domain boundary or when splitting would create artificial fragments. When split, `docs/mof/<domain>.md` holds that domain's Responsibilities, Functions, Entities, Events, and intra-domain Relationships; `docs/MOF.md` remains the global source for Metadata, the complete `impact_index`, the domain registry, cross-domain Relationships and Impact Rules, Cross-Cutting Rules, Open Questions, and Revision History.6768**`impact_index` never splits.** It stays whole in `docs/MOF.md` covering every domain, so a cross-domain traversal still costs one file read. Never duplicate a Responsibility across two files.6970When a split map changes, update the owning domain file and the root `docs/MOF.md` in the same change. Keep domain-local responsibilities, functions, entities, events, and relationships in their domain file; keep cross-domain edges and global projections in the root file. A Responsibility has one home only, and the root index must be regenerated from all domain files after every structural change. The root domain registry is the authoritative file-to-domain mapping; do not infer ownership from folder names alone.7172### After a change lands7374Update the owning domain file and the root `docs/MOF.md` when the map is split, or only `docs/MOF.md` when it is not split:7576- Affected Responsibilities (their description, interfaces, side effects, state), and the `role`, `nature`, `srp_status`/`srp_rationale`, and `ocp_status`/`ocp_rationale` of any affected Function77- `relationships[]` created, changed, or removed78- `entities[]` / `events[]` where the change altered who reads, writes, publishes, or consumes79- Impact rules the change invalidated or created80- **`impact_index` lines for every touched Responsibility**, regenerated from the sections above81- **`mof_meta.last_updated`** and **`mof_meta.last_commit`**, and `version` when the change is structural — Query step 2 uses both commit and timestamp evidence, so leaving either stale makes the map untrusted82- Revision history (version, date, commit, summary)8384The MoF is stale when it no longer reflects the system's behavior. Stale documentation is worse than none: it leads the agent to incorrect assumptions.8586---8788## Query — before changes8990Mandatory before proposing any code change that touches logic, contracts, or behavior. Purely cosmetic changes (typos, formatting, comments, doc text) skip straight to the edit.9192**Read `mof_meta` and `impact_index` first.** They locate seeds, expose the raw freshness evidence (`last_updated` and `last_commit` — there is no stored freshness verdict to trust), and provide the first traversal projection. Then open only the relevant `relationships[]`, `entities[]`, `events[]`, `workflows[]`, `impact_rules[]`, and `cross_cutting[]` entries needed to complete the radius; do not load the whole `docs/MOF.md`, and do not open any Responsibility's full block until it is already inside the computed radius. This is the flow, not a size-dependent optimization.93941. **Locate the seeds.** Grep `impact_index` for the task's subject: capability name, `code_ref` path, or domain. A `Function` id resolves to every Responsibility it groups.952. **Check freshness, scoped to the seeds.** With the seeds' `code_ref` paths known, compare the latest relevant commit with `mof_meta.last_commit` and compare commit timestamps with `mof_meta.last_updated`. Classify each seed as `fresh` when both pieces of evidence are covered, `stale` when relevant code is newer, or `unverified` when a path, commit, or timestamp cannot be proved. Run a mini Map cycle only for a stale seed. Never freshness-check the whole repo — a repo-wide `git log` reports stale after any commit anywhere.963. **Traverse the call graph.** From each seed, follow `exp:` (downstream) to **depth 2**. Read the relevant relationship records to determine `criticality`; go deeper along an edge only when it is `critical`. **One exception runs upstream:** when a seed belongs to a Function with `nature: abstract`, also follow its `dep:` along `implements`/`extends` edges — a contract's implementors are upstream of it, and they all break when the contract changes, so `exp:` alone would report an interface change as affecting nothing. Record the depth reached and the relationship evidence. If the radius swallows most of the map, that is a failed query — say so and narrow the change instead of reporting the whole system as affected.974. **Fan out through state** — two paths the call graph cannot see:98 - **Entities.** For each in-radius Responsibility writing an entity (`ent:` marked `(w)`), read the relevant entity record and add every Responsibility that reads it. A writer's behavior change breaks its readers with no call edge between them.99 - **Events.** For each in-radius Responsibility publishing an event (`evt:` marked `+`), read the relevant event record and add its `consumed_by`. Loose coupling still transmits breakage.1005. **Check artifact quality exposure.** Look at `srp:` and `ocp:` on each in-radius line. `srp:violation` means the Responsibility physically shares a code artifact with siblings that implement independent reasons for change; `ocp:violation` means the artifact contains a type-discriminant branch a new variant would grow. Either `unverified` status means that exposure cannot yet be trusted. Pull the Function's siblings into review and report the evidence instead of inferring a violation.1016. **Check workflows.** Any `workflows[]` whose `sequence` contains an in-radius Responsibility needs end-to-end review — a change can be locally correct and still break the flow's contract.1027. **Apply impact rules.** Take the `ir:` ids from each in-radius index line and read those rules. Their `recommended_actions` are required work, not suggestions.1038. **Apply cross-cutting rules.** Check `cross_cutting` for rules binding the in-radius Responsibilities. A change that satisfies its Responsibility and violates a cross-cutting rule is a defect. For a `kind: architecture` rule, also check every in-radius relationship it binds: a cross-domain edge whose target Function has `nature: concrete` where the rule requires an abstraction is a live DIP violation.1049. **Only now read the details** — for the in-radius Responsibilities and only those: `responsibilities`, `non_responsibilities`, `interfaces`, `side_effects`, `state`, `notes`. With `interfaces` open, also resolve LSP and ISP for any in-radius relationship that qualifies: for an `implements`/`extends` edge, compare the `interfaces.inputs`/`outputs` of `from` and `to` — a narrowed precondition, widened postcondition, or a new exception the supertype doesn't declare is a violation; for any edge carrying `consumed_interfaces`, compare it against the target Function's full `responsibilities[]` — a small, tightly-coupled subset of a much larger surface is a violation. Report both as live reads, never as a cached status.10510. **Derive actions:** tests to update, contracts to review, documentation and ADRs to create.10611. **Only then** propose the code, respecting each Responsibility's `responsibilities` and `non_responsibilities`.107108The Query result must distinguish verified facts from assumptions and report the evidence path for every item added to the radius. If a relationship references a missing Responsibility, entity, event, workflow, or rule, preserve the identifier and mark it `unresolved` rather than silently dropping it.109110If the task touches code absent from the MoF, run a mini Map cycle for it first.111112### Agent response pattern113114Make the plan explicit:115116- **Responsibilities involved:** `RESP_...`, grouped under their `F_...`117- **Blast radius:** downstream Responsibilities requiring review, and the **traversal depth reached**118- **Reached via state:** Responsibilities pulled in by entity or event fan-out, not by a call edge119- **Shared-code exposure:** sibling Responsibilities pulled in by a `srp_status` or `ocp_status` of `violation` or `unverified`, not by any edge120- **SOLID decisions:** `role`, `nature`, `srp_status`/`srp_rationale`, and `ocp_status`/`ocp_rationale` for every affected Function; for LSP and ISP, the live read for each qualifying in-radius relationship (or "not applicable" when no `implements`/`extends`/`consumed_interfaces` edge is in radius); for DIP, the `architecture` rule checked and its verdict121- **Workflows affected:** `W_...`122- **Impact rules triggered:** `IR_...`123- **Cross-cutting rules in play:** which, and how the change satisfies them124- **Evidence status:** `verified`, `unverified`, or `unresolved` for each relationship path125- **Intended actions:** tests, contracts, documentation126127---128129## Visualize130131Only on explicit user request ("visualize the MoF", "generate the technical report", "check for SOLID violations", "audit the map") — never automatically after Map. Generating the HTML on every code change would add noise and unnecessary work.132133Produces `docs/MOF.html`: a static single-file technical workbench with metadata, global search, Function selection, A/B/C navigation, domains, Functions, Responsibilities, technical relationships, entities, events, workflows, impact rules, shared rules, open questions, a complete footer legend, and print-friendly CSS. A request framed as a checkup or audit gets the same output — the Functions table's `role`, `nature`, `srp_status`/`srp_rationale`, and `ocp_status`/`ocp_rationale` provide the artifact-level SOLID view; the Relationships table's `type`, `consumed_interfaces`, and the target's `nature` give the evidence for a manual LSP/ISP/DIP read. The page shell is a fixed asset ([`mof-shell.html`](mof-shell.html)) that you copy and fill at its markers — never retype it. Fill procedure: [`visualization.md`](visualization.md).134135---136137## `docs/MOF.md` document template138139Full structure (Metadata, Impact Index, Responsibilities, Functions, Entities, Events, Workflows, Relationships, Impact Rules, Cross-Cutting Rules, Open Questions, Revision History): [`mof-template.md`](mof-template.md). Used by Map.140141---142143## Maturity criterion144145The MoF is mature when it lets an agent **understand → reason → plan → implement → validate → document** a change with minimal additional context, when every Responsibility with side effects has at least one impact rule, when `exp:` in `impact_index` is verified against the real code, when every Function's `srp_status` and `ocp_status` reflect a deliberate choice rather than an accident of how the code grew, when every `implements`/`extends`/`consumed_interfaces` edge carries enough evidence for a live LSP/ISP read and every cross-domain edge has been checked against an `architecture` rule for DIP, and when a Query resolves a blast radius **without reading a single Responsibility's full block outside that radius**.