diff --git a/.gitignore b/.gitignore
index 2476e52091..ec18e4d2a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -215,5 +215,6 @@ DataTreeTimingBaselines.json
/findings_*.md
*scratchpad*
*[Ss]cratchPad*
+/.review/
Docs/migration/working/
diff --git a/CONTEXT.md b/CONTEXT.md
index 475e541369..c55b89971c 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -17,6 +17,10 @@ It is intentionally not a full architecture manual. It should stay biased toward
- **MSBuild project**: A `.csproj`, `.vcxproj`, `.wixproj`, or similar build unit.
- **Installer project**: WiX authoring and packaging work under `FLExInstaller/`.
- **Worktree**: A git worktree for isolated builds and edits.
+- **`Grammar`**: overloaded — qualify when the distinction matters.
+ - **Grammar Area**: One of the five top-level Areas (see Core Domain section) — the Parts of Speech/Features/etc. tool group.
+ - **Grammar Sketch**: An existing human-readable, published linguistic-description document export (`ExportDialog.ExportGrammarSketch`); unrelated to the HC grammar below despite the shared word.
+ - **HC grammar**: The HermitCrab-format grammar (phonology, morphology, features, categories) produced from a language project via `HCLoader.Load` + `XmlLanguageWriter.Save` (`SIL.Machine.Morphology.HermitCrab`). Always say "HC grammar," never bare "grammar," when the distinction from the other two matters.
- **`Writing system`**: overloaded — qualify when the distinction matters.
- **Writing system definition**: `WritingSystemDefinition` (libpalaso / `SIL.WritingSystems`); the base writing system class, identified by a BCP-47 language tag.
- **Core writing system definition**: `CoreWritingSystemDefinition` (liblcm / `SIL.LCModel.Core.WritingSystems`); extends `WritingSystemDefinition` with a `Handle` and LCM-specific features such as character sets.
@@ -33,6 +37,7 @@ It is intentionally not a full architecture manual. It should stay biased toward
- **IME composition**: The transient input-method editing state before text is committed. Treat composition behavior and committed text behavior as separate test and parity concerns.
- **Lexicon**: The lexical data and editing experience in FLEx.
- **Interlinear text**: Text annotated with multiple aligned linguistic analysis lines.
+- **Word occurrence** (`IAnalysis`, in `SIL.LCModel`): One tokenized position in a paragraph — either a bare, unanalyzed **wordform occurrence** (`IWfiWordform`, `HasWordform == true` but no morphological breakdown chosen) or an **analyzed occurrence** (`IWfiAnalysis`/`IWfiGloss` — a morphological breakdown/gloss has been attached, whether by a human or an unreviewed parser guess; "analyzed" says nothing about whether the analysis is linguistically correct). Punctuation occurrences also satisfy `IAnalysis` but not `HasWordform`. This three-way distinction (punctuation / wordform-only / analyzed) recurs across `StatisticsView`, `ConcordanceControl`, and Interlinear editing — use these names rather than inventing new ones.
- **Morphology**: The part of the system and data model concerned with morphemes, rules, and word analysis.
- **Parser**: Morphological analysis tooling such as HermitCrab or XAmple.
- **Paratext integration**: Scripture and lexicon interoperability with Paratext. Implemented across `FwParatextLexiconPlugin`, `ParatextImport` (scripture text import via `ParatextImportManager`/`ParatextSfmImporter`), and `Paratext8Plugin` (bridge to Paratext APIs).
@@ -53,7 +58,7 @@ It is intentionally not a full architecture manual. It should stay biased toward
- **Service locator**: `LcmCache.ServiceLocator` (`ILcmServiceLocator`). IoC container for LCModel — the primary way to retrieve repositories, factories, and services.
- **Unit of work**: Groups data-model changes under `IActionHandler`. All LCModel writes must occur inside one. `UndoableUnitOfWorkHelper` (undoable) and `NonUndoableUnitOfWorkHelper` (non-undoable) are in `SIL.LCModel.Infrastructure`; use as a `using` block or via their static `.Do(...)` helpers.
- **Publish/subscribe system**: Messaging system (`IPublisher` / `ISubscriber`, `SIL.FieldWorks.Common.FwUtils`) via `FwUtils.Publisher` and `FwUtils.Subscriber` singletons. Supports exact-name and prefix subscriptions; `PublishAtEndOfAction` defers delivery to end of user action. Event-based problems deserve event-based solutions — avoid state variables for event timing when a deterministic subscribe/unsubscribe solution can be used.
-- **Area**: One of the five top-level navigation divisions — Lexicon, Grammar, Words & Texts, Notebook, Lists. Declared in `areaConfiguration.xml` and identified by constants in `AreaConstants`. Each Area has its own sidebar and owns a set of Tools.
+- **Area**: One of the five top-level navigation divisions — Lexicon, Grammar, Texts & Words, Notebook, Lists. Declared in `areaConfiguration.xml` and identified by constants in `AreaConstants`. Each Area has its own sidebar and owns a set of Tools. (The Texts & Words area's internal `areaChoice` value is `textsWords`.)
- **Tool**: A view or function within an Area, declared in `toolConfiguration.xml`. The active Tool per Area is tracked via `ToolForAreaNamed_`; switching Areas restores the last-used Tool. Navigation is property-driven through the XCore mediator system.
- **Dictionary configuration**: A `.fwdictconfig` XML file (`DictionaryConfigurationModel`) defining which LCModel fields appear in a dictionary view, their order, style, and options. Scoped to one or more Publications.
- **Publication**: A named output target (e.g. a print edition or web view) that dictionary configurations are scoped to. `AllPublications` applies a configuration to all current and future publications.
diff --git a/DistFiles/Language Explorer/Configuration/Main.xml b/DistFiles/Language Explorer/Configuration/Main.xml
index edd2bff59e..011dcf1663 100644
--- a/DistFiles/Language Explorer/Configuration/Main.xml
+++ b/DistFiles/Language Explorer/Configuration/Main.xml
@@ -921,6 +921,9 @@ I (RandyR) brought them into this file, just to keep track of them for the time
populates the "tools" list appropriately. -->
+
+
diff --git a/DistFiles/Language Explorer/Export Templates/AIExportInstructions.md b/DistFiles/Language Explorer/Export Templates/AIExportInstructions.md
new file mode 100644
index 0000000000..74cbc2ccbe
--- /dev/null
+++ b/DistFiles/Language Explorer/Export Templates/AIExportInstructions.md
@@ -0,0 +1,76 @@
+# Read this first: a FieldWorks grammar and texts for analysis
+
+This folder was exported from FieldWorks Language Explorer (FLEx) by its "Export Grammar and
+Texts for AI Analysis" command. Everything in it was put here deliberately for an AI assistant
+(ChatGPT, Claude, or otherwise) to read, including this file.
+
+## What is in this folder
+
+- `HCGrammar.xml` -- the project's **HC grammar**: its HermitCrab-format grammar (phonology,
+ morphology, features, categories), the same shape the HermitCrab parser itself consumes to
+ parse words. This is the parser's own input, not a human-readable grammar description.
+- One `.flextext` file per exported text -- FLExText, FieldWorks' interlinear-text interchange
+ format. These are real corpus texts the grammar is supposed to account for. Any given text may
+ be fully analyzed, partly analyzed, or not analyzed at all.
+- `export-instructions.md` -- this file.
+
+## Before you answer, read the reference
+
+Grammars and interlinear texts have real gotchas you will not know about on your own. Fetch and
+read this first:
+
+```
+https://raw.githubusercontent.com/sillsdev/FieldWorks/main/Docs/ai-parser-help/README.md
+```
+
+That is the index for a reference covering correctness gotchas (`broken/`), performance gotchas
+(`speed/`), grammar-authoring methodology (`workflow/`), and the FLExText format itself,
+including the `analysisStatus` ground-truth caveat (`texts/`). Use the raw URL rather than the
+`github.com/.../blob/...` page -- it returns plain markdown that fetches cleanly. Follow whichever
+of its links bear on the question you were asked, and reason about this project's grammar against
+the engine's documented mechanics rather than guessing.
+
+## How to answer: plain language, for a linguist
+
+The person asking is a field linguist with a real language project open in FLEx. They know their
+language; they usually do not know HermitCrab's internals, and they did not ask for a tour of
+them. An answer that reads like engine documentation gets abandoned, however correct it is.
+
+- **Accuracy first, then plain language.** An answer that misstates the engine is worse than no
+ answer, so never simplify to the point of being wrong. When the honest answer is complicated,
+ give the short version first and the detail underneath.
+- **No unexplained jargon.** Use a technical term only if the answer genuinely needs it, and
+ define it in ordinary words the first time: "MPR feature (a label you put on a morpheme so that
+ rules can test for it)". Never leave an abbreviation bare -- write "morphosyntactic property",
+ not "MSP".
+- **Use the words FLEx uses.** Name what the user actually clicks (the Grammar area, an affix
+ template, a slot, an allomorph, an inflection class) rather than the engine's internal class or
+ field name, so they can find it. Describe fixes as things to do in FLEx, not as XML to
+ hand-edit.
+- **Lead with what to do.** Open with the change to make, in a sentence or two. Put the mechanism
+ -- why the engine behaves this way -- after it, for the reader who wants it.
+- **Show rather than lecture.** One worked example with a real surface form beats a paragraph of
+ theory.
+- **Short sentences.** Cut "it is important to note that", "as mentioned above", and any
+ restatement of what you just said.
+- **Flag every guess, every time.** Name what you are unsure about and what would settle it (a
+ specific word to parse, a trace to look at). The user cannot see your reasoning, so an unmarked
+ guess reads exactly like a fact, and a confident wrong answer costs them real work.
+- **Each answer stands alone.** Do not lean on earlier turns in the chat, or on file names in the
+ reference, as though the user had them open.
+
+## For the person who exported this folder
+
+**This is AI. It can be very wrong, and it can be wrong while sounding certain.** Treat anything
+it tells you about your grammar or your texts as a suggestion to check against your own data,
+never as a finding. Understand a change, and confirm it yourself by re-parsing the words you care
+about, before you apply it to your project.
+
+Your exported files are real linguistic data about a real language project. Dropping them into a
+third-party chat service sends that data to that provider (OpenAI, Anthropic, and so on), so check
+your project's data-sensitivity policy before sharing an unpublished or restricted grammar or text
+this way.
+
+A fuller human-readable walkthrough, including example questions worth asking, is at:
+
+
diff --git a/DistFiles/Language Explorer/Export Templates/GrammarAndTextsForAI.xml b/DistFiles/Language Explorer/Export Templates/GrammarAndTextsForAI.xml
new file mode 100644
index 0000000000..0e1a7c0c73
--- /dev/null
+++ b/DistFiles/Language Explorer/Export Templates/GrammarAndTextsForAI.xml
@@ -0,0 +1,11 @@
+
+
+ Export Grammar and Texts for AI Analysis
+
+WARNING: this is AI. It can be very wrong, and it can be wrong while sounding certain. Treat anything it tells you about your grammar or your texts as a suggestion to check against your own data, never as a finding. Understand a change, and confirm it yourself, before you apply it to your project.
+
+Writes this project's HermitCrab grammar (HCGrammar.xml), one .flextext file per selected text, and an export-instructions.md file into a folder you choose.
+
+Then drag and drop all the files into ChatGPT (or Claude) and ask your question. The export-instructions.md file carries the reference links and reading instructions the AI needs, so there is nothing else for you to paste.
+
diff --git a/Docs/ai-parser-help/README.md b/Docs/ai-parser-help/README.md
new file mode 100644
index 0000000000..a8a462e178
--- /dev/null
+++ b/Docs/ai-parser-help/README.md
@@ -0,0 +1,110 @@
+# AI Parser Help — HermitCrab reference for LLMs
+
+This is a living reference for asking an LLM (ChatGPT, Claude, or otherwise) questions about
+**HermitCrab**, the rule-based morphological parser/generator implemented in this repository
+(`sillsdev/machine`, namespace `SIL.Machine.Morphology.HermitCrab`), and about **FLExText**,
+FieldWorks' interlinear-text interchange format for the connected corpus texts a grammar is
+supposed to account for. It covers three kinds of question a grammar author actually asks about
+HermitCrab itself, plus a fourth section for a different kind of upload entirely — real corpus
+texts rather than the grammar:
+
+- **[`broken/`](broken/README.md)** — "why is this wrong / missing / crashing?" Correctness
+ gotchas: wrong parses, missing parses, crashes, and silent misconfigurations.
+- **[`speed/`](speed/README.md)** — "why is this slow?" Performance gotchas: combinatorial
+ blowups and other parse-time costs.
+- **[`workflow/`](workflow/README.md)** — "how should I approach modeling this?" Authoring
+ guidance for building a grammar well in the first place, grounded in HermitCrab's actual
+ mechanics and in H. Andrew Black's FLEx parsing methodology (primary source included verbatim
+ under `workflow/sources/`).
+- **[`texts/`](texts/README.md)** — "I also have real corpus texts, not just a grammar." A
+ reference for FLExText, FieldWorks' interlinear-text interchange format: what it is, how to
+ extract it, and how to get an LLM to reason over it correctly, including the `analysisStatus`
+ ground-truth caveat and which AI products can actually run code against your uploaded file.
+
+Each file covers one topic in enough depth to answer questions about that topic without needing
+local access to the repo — code excerpts, mechanisms, and worked examples are inlined.
+
+## Got a FieldWorks grammar and a question?
+
+Send that person **this link instead**: [`getting-started.md`](getting-started.md) — it walks
+through extracting your grammar as HermitCrab XML and getting ChatGPT/Claude to reason about it
+using this reference. The rest of this README is the reference material itself (for the LLM to
+read), not the human-facing walkthrough.
+
+Got interlinear **texts** (`.flextext` files) instead of, or in addition to, a grammar? See
+[`texts/getting-started.md`](texts/getting-started.md) instead — extracting and reasoning about
+connected corpus texts is a different workflow from the grammar one above.
+
+## How to use this with an LLM
+
+Paste the **raw** URL of the relevant topic file into your chat, e.g.:
+
+```
+https://raw.githubusercontent.com/sillsdev/machine/master/docs/ai-parser-help/speed/affix-template-optional-slots.md
+```
+
+Then ask your question. Use the raw URL (`raw.githubusercontent.com`), not the normal
+`github.com/.../blob/...` page — the raw URL returns plain markdown text with no site chrome,
+which fetches cleanly for both ChatGPT (web browsing) and Claude (WebFetch) without JS rendering
+or auth. If you're not sure which topic file is relevant, paste this README's raw URL first, or
+whichever of `broken/README.md`, `speed/README.md`, `workflow/README.md`, `texts/README.md` best
+matches your question ("why is this wrong" vs. "why is this slow" vs. "how should I model this"
+vs. "I have corpus texts, not just a grammar") — an LLM that can follow links will use it as an
+index; otherwise, browse the lists yourself.
+
+Do not use these guides as a source of real grammar or text data — see "What belongs here" below.
+
+## How to answer: plain language, for a linguist
+
+Instructions for the LLM reading this reference. The person asking is a field linguist with a
+real language project open in FLEx. They know their language; they usually do not know
+HermitCrab's internals, and they did not ask for a tour of them. An answer that reads like engine
+documentation gets abandoned, however correct it is.
+
+- **Accuracy first, then plain language.** An answer that misstates the engine is worse than no
+ answer, so never simplify to the point of being wrong. When the honest answer is complicated,
+ give the short version first and the detail underneath.
+- **No unexplained jargon.** Use a technical term only if the answer genuinely needs it, and
+ define it in ordinary words the first time: "MPR feature (a label you put on a morpheme so that
+ rules can test for it)". Never leave an abbreviation bare -- write "morphosyntactic property",
+ not "MSP".
+- **Use the words FLEx uses.** Name what the user actually clicks (the Grammar area, an affix
+ template, a slot, an allomorph, an inflection class) rather than the engine's internal class or
+ field name, so they can find it. Describe fixes as things to do in FLEx, not as XML to hand-edit.
+- **Lead with what to do.** Open with the change to make, in a sentence or two. Put the mechanism
+ -- why the engine behaves this way -- after it, for the reader who wants it.
+- **Show rather than lecture.** One worked example with a real surface form beats a paragraph of
+ theory.
+- **Short sentences.** Cut "it is important to note that", "as mentioned above", and any
+ restatement of what you just said.
+- **Flag every guess, every time.** Name what you are unsure about and what would settle it (a
+ specific word to parse, a trace to look at). The user cannot see your reasoning, so an unmarked
+ guess reads exactly like a fact, and a confident wrong answer costs them real work.
+- **Each answer stands alone.** Do not lean on earlier turns in the chat, or on file names in this
+ reference, as though the user had them open.
+
+## What belongs here
+
+- General HermitCrab engine mechanics: how rules, strata, templates, features, and the
+ analysis/synthesis engines work. This is documentation of the open-source parser itself.
+- FLEx/HermitCrab grammar-authoring methodology, grounded in the engine's actual behavior.
+- FLExText format documentation, grounded in the FieldWorks schema/exporter source that produces
+ it — see [`texts/`](texts/README.md).
+- Synthetic/toy grammar snippets used purely to illustrate a mechanism (e.g. `p1`..`p12`,
+ `sg`/`pl` × invented cases) are fine, as are synthetic/invented interlinear-text examples.
+- **Not** real grammar or text data for any specific language (e.g. Sena, Amharic, Indonesian,
+ Aweti). Those grammars — and any real corpus texts from those projects — are private and must
+ never be committed to this repo — see the project's existing grammar-privacy constraints. If a
+ question requires reasoning about a real grammar or text, describe the relevant structure
+ abstractly instead of pasting the real rules or sentences.
+
+## Source grounding
+
+Claims in `broken/`, `speed/`, and `workflow/` are grounded in the actual engine source under
+`src/SIL.Machine.Morphology.HermitCrab/` and `src/SIL.Machine/` as of the commit each file was
+last updated — each file's metadata header names the specific source file(s) it's grounded in.
+Claims in `texts/` are grounded in the FieldWorks source that defines and produces FLExText, plus
+cited external documentation — see [`texts/README.md`](texts/README.md#source-grounding) for
+specifics; unlike the other three sections, `texts/` cites other repositories and external sources
+directly, since explaining an interchange format requires it. File/line references may drift as
+the code evolves; if something looks stale, check the live source at the paths cited.
diff --git a/Docs/ai-parser-help/broken/README.md b/Docs/ai-parser-help/broken/README.md
new file mode 100644
index 0000000000..925933b748
--- /dev/null
+++ b/Docs/ai-parser-help/broken/README.md
@@ -0,0 +1,53 @@
+# HermitCrab correctness gotchas
+
+Part of the [HermitCrab-for-LLMs reference](../README.md). One file per gotcha — each is
+self-contained, cites the specific `machine` source file(s) (and, where the trap originates in how
+FieldWorks compiles a grammar into HermitCrab's XML, the specific `FieldWorks` source file(s)) that
+implement the relevant behavior, and gives a fix. This section covers *correctness* problems — wrong
+parses, missing parses, crashes, and silent misconfigurations — as opposed to the sibling
+[`speed/`](../speed/README.md) section, which covers performance.
+
+Each file starts with a metadata header:
+
+```yaml
+---
+title:
+implements:
+category:
+symptom:
+grammar_visible:
+---
+```
+
+## Index
+
+| Gotcha | Category | Symptom | Description |
+|---|---|---|---|
+| [A StemName's partsOfSpeech is required — omitting it crashes, it doesn't silently reject everything](stemname-partsofspeech-required-not-silent.md) | loader | crash | An omitted `partsOfSpeech` on `` fails the grammar load outright (DTD validation or a `NullReferenceException`), not a silent empty-POS constraint. |
+| [A rule's requiredStemName is checked in the forward (synthesis) pass only — including during parsing](stem-name-affix-requirement-trace-misreading.md) | lexicon | silent-misconfiguration | `AffixProcessRule.RequiredStemName` is never read during analysis-side unapplication; it's enforced later in the mandatory resynthesis/confirmation pass, so a trace reader looking only at analysis-rule entries sees no rejection. |
+| [LexFamily suppletion blocking silently discards a regular derivation, and only during generation](lexfamily-blocking-generation-only.md) | lexicon | missing-parse | `Blockable` rules can substitute a suppletive family member's form for a regularly-derived one via feature-structure `Subsumes`, which is checked only on synthesis and can fire far more broadly than one paradigm cell if the irregular entry's own feature structure is under-specified. |
+| [An MPR feature group's match type defaults to Any (OR), not All](mpr-group-matchtype-default-is-any.md) | feature-system | silent-misconfiguration | Omitting `matchType` on a feature group gives OR semantics (any one listed feature suffices), not AND — the opposite of what an author wanting conjunctive gating might assume. |
+| [An MPR feature group's output type defaults to Overwrite, not Append](mpr-group-output-default-overwrite.md) | feature-system | silent-misconfiguration | Omitting `outputType` gives non-monotone "last rule wins" behavior by default, silently dropping an earlier rule's tag from the same group. |
+| [A stem with no inflection class and no configured default silently fails every class-restricted rule](inflection-class-no-default-silent-gating.md) | feature-system | missing-parse | An untagged stem with no `DefaultInflectionClassRA` configured anywhere up its POS hierarchy gets no inflection-class MPR feature at all, so every rule gated on any inflection class fails for it — not "matches the wrong class," matches no class. |
+| [RealizationalRule can only spell out features already present — it cannot assign new ones the way AffixProcessRule can](realizational-rule-cannot-add-features.md) | morphotactics | silent-misconfiguration | `RealizationalRule` has no `OutputHeadFeatures`/`outputPartOfSpeech` equivalent; it can only merge a `RealizationalFeatureStruct` into a word, never introduce a feature value the word doesn't already carry in some form. |
+| [RealizationalAffixProcessRule has no MaxApplicationCount backstop, unlike AffixProcessRule](realizational-rule-no-application-cap.md) | morphotactics | wrong-parse | Unlike `AffixProcessRule`/`CompoundingRule`, this rule type has no per-rule application cap at all — only a feature-based `IsBlocked` check stands between it and reapplying indefinitely in one derivation. |
+| [A null/zero-realization affix is a real rule application, not a free default value](null-affix-cannot-express-default.md) | morphotactics | wrong-parse | A "null suffix" modeling a default value still goes through the same required-feature/MPR/environment gates as any other rule in a mandatory slot — if those gates aren't unconditioned, the derivation dead-ends instead of falling back to the intended default. |
+| [Independent optional slots in one template can license a combination two templates would have prevented](single-template-independent-slots-illegal-combos.md) | morphotactics | wrong-parse | Each optional slot's apply-or-skip choice is explored independently of every other slot in the same template, so two affixes meant to always co-occur can each fire without the other unless a grammar author adds an explicit feature gate or splits them into separate templates. |
+| [An unclassified (or under-specified) affix rule bypasses normal template-ordering discipline](unclassified-affix-bypasses-template-ordering.md) | morphotactics | wrong-parse | FLEx's "unclassified" affix status (and an inflectional affix left with no assigned slot) compiles to `IsPartial = true`, which is specifically permitted to attach after a final template where a normal classified rule would be refused. |
+| [A multi-morpheme co-occurrence exclusion requires all listed morphemes together — not any one of them](coocurrence-rule-requires-all-not-any.md) | morphotactics | wrong-parse | An ad hoc prohibition listing several "other" morphemes only blocks the key morpheme when *all* of them co-occur together in the same word, not when any single one does — a much weaker constraint than a list of independent pairwise exclusions. |
+| [Modeling a discontinuous morpheme as two independent affixes loses the engine's atomic-circumfix guarantee](circumfix-as-two-affixes-loses-atomicity.md) | morphotactics | wrong-parse | HC's native circumfix process applies both parts atomically or not at all; modeling the same discontinuous morpheme as two independent affixes in two template slots gives up that guarantee and can license a one-sided (half-realized) form. |
+| [Compounding is capped at one application per derivation by default, and exocentric compounding cannot be configured otherwise](compounding-max-application-count-default.md) | compounding | missing-parse | `CompoundingRule.MaxApplicationCount` defaults to 1; FieldWorks's loader only ever raises it for endocentric compound rules, so a recursive (three-or-more-element) compounding pattern modeled as exocentric can never be configured to recurse. |
+| [A stem-name-restricted allomorph needs the feature explicitly assigned, not just compatible](stem-name-explicit-feature-requirement.md) | lexicon | silent-misconfiguration | `StemName.IsRequiredMatch` tests whether a feature is *explicitly present* on the word, not whether the word is merely compatible with (doesn't conflict with) the region — an unmarked form fails a stem-name-restricted allomorph even trivially. |
+
+## How to use this with an LLM
+
+Paste the raw URL of the specific gotcha file that matches your symptom, e.g.:
+
+```
+https://raw.githubusercontent.com/sillsdev/machine/master/docs/ai-parser-help/broken/lexfamily-blocking-generation-only.md
+```
+
+If you're not sure which one applies, paste this index's raw URL first and describe your grammar's
+structure (not its actual rules — see the privacy note in the top-level [`README.md`](../README.md))
+and symptom (e.g. "this word parses when it shouldn't," "the grammar won't load," "an affix I marked
+inflectional shows up in a position I didn't expect").
diff --git a/Docs/ai-parser-help/broken/circumfix-as-two-affixes-loses-atomicity.md b/Docs/ai-parser-help/broken/circumfix-as-two-affixes-loses-atomicity.md
new file mode 100644
index 0000000000..64ef28d464
--- /dev/null
+++ b/Docs/ai-parser-help/broken/circumfix-as-two-affixes-loses-atomicity.md
@@ -0,0 +1,63 @@
+---
+title: "Modeling a discontinuous morpheme as two independent affixes loses the engine's atomic-circumfix guarantee"
+implements: src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AffixProcessRule.cs, FieldWorks Src/LexText/ParserCore/HCLoader.cs
+category: morphotactics
+symptom: wrong-parse
+grammar_visible: yes
+---
+
+## What it is
+
+H. Andrew Black's FLEx/HC conceptual intro (§4.3 "Circumfixes"; process-rule mechanics in §6.1.1.3
+"Circumfixation as a process") describes two ways to model a discontinuous morpheme whose prefix
+part and suffix part always co-occur as a single meaning: (a) one atomic circumfix lexical entry, or
+(b) two independent affix entries, one placed in a prefix slot and one in a suffix slot of the same
+template. HC's engine genuinely supports circumfixation as one of its native affix processes
+(`AffixProcessRule.cs:14-17` documents "prefixation, suffixation, infixation, circumfixation,
+simulfixation, reduplication, and truncation" as the process types one rule can express), and
+FieldWorks's loader compiles a circumfix lexical entry into a single, atomic `AffixProcessAllomorph`
+whose pattern spans the stem with the prefix part on one side and the suffix part on the other
+(`LoadCircumfixAffixProcessAllomorph`, FieldWorks `Src/LexText/ParserCore/HCLoader.cs:1273`). Option
+(b) — two separate affixes — does not get this atomicity, and the engine has no other mechanism that
+supplies it.
+
+## The mechanism
+
+A circumfix loaded as one `AffixProcessAllomorph` (option a) either applies as a whole — inserting
+both the prefix and suffix material together, gated by one shared set of required
+features/environment/MPR conditions — or does not apply at all. There is no way for "half a
+circumfix" to appear in a derivation, because the engine only ever sees one rule/allomorph unit for
+it.
+
+Modeled as two ordinary `AffixProcessRule`s in independent template slots (option b), each half is
+an entirely separate `Morpheme`/`AffixProcessRule` with its own `RequiredSyntacticFeatureStruct`,
+its own environment, its own MPR-feature gates — nothing in the engine ties their applicability
+together. If the two rules' gates aren't kept in exact lockstep by the grammar author (same required
+features, same MPR-feature requirements, same environment conditions restated on both), the engine
+can apply one half without the other: the prefix rule's slot fires while the suffix rule's slot (in
+the same optional-slot cross-multiplication described in the companion single-template gotcha) does
+not, or vice versa — see `single-template-independent-slots-illegal-combos.md` for the exact
+mechanism by which independent optional slots explore every combination, including one-sided ones.
+
+## Concrete example
+
+A causative meaning is realized as a circumfix `ka-...-an` in one language design. Modeled as two
+independent affixes — a prefix rule for `ka-` and a suffix rule for `-an`, each in its own optional
+template slot, each carrying the same `cause=true` required/output feature by the author's intent —
+a later grammar edit that updates the prefix rule's required feature structure (e.g. narrowing it to
+a particular verb subclass) without making the identical edit to the suffix rule's required feature
+structure silently breaks the coupling: forms with `ka-` but no `-an`, or `-an` but no `ka-`, become
+derivable/parseable, even though the language never realizes the causative as anything but the whole
+circumfix. Modeled instead as one atomic circumfix entry, the same edit to "narrow which subclass
+gets the causative" only has one place to make it, and there is no way to get a half-realized
+circumfix out of the engine.
+
+## Fix
+
+Model a true discontinuous morpheme (one meaning realized as two non-adjacent surface parts that
+always co-occur) as a single circumfix lexical entry/`AffixProcessAllomorph`, not as two independent
+affixes in two template slots. If two independent affixes are used anyway (e.g. because the two
+parts have genuinely independent distributions in some contexts), keep every gating condition
+(required features, MPR features, environment) that couples them duplicated exactly and re-verified
+on every edit to either rule — the engine provides no shared-identity mechanism to keep them in sync
+automatically.
diff --git a/Docs/ai-parser-help/broken/compounding-max-application-count-default.md b/Docs/ai-parser-help/broken/compounding-max-application-count-default.md
new file mode 100644
index 0000000000..adfd7f264f
--- /dev/null
+++ b/Docs/ai-parser-help/broken/compounding-max-application-count-default.md
@@ -0,0 +1,72 @@
+---
+title: "Compounding is capped at one application per derivation by default, and exocentric compounding cannot be configured otherwise"
+implements: src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/CompoundingRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisCompoundingRule.cs
+category: compounding
+symptom: missing-parse
+grammar_visible: partially
+---
+
+## What it is
+
+`CompoundingRule.MaxApplicationCount` defaults to `1` (`CompoundingRule.cs:19`), enforced the same
+way an `AffixProcessRule`'s cap is: `SynthesisCompoundingRule.Apply` refuses to apply the rule again
+once `input.GetApplicationCount(_rule) >= _rule.MaxApplicationCount`
+(`SynthesisCompoundingRule.cs:50-62`). A grammar author modeling a language that allows more than two
+elements in a compound (e.g. a three-noun compound built by applying the same compounding rule
+twice) gets exactly one level of compounding per derivation unless something explicitly raises this
+cap — and FieldWorks's compiler only gives *endocentric* compound rules a way to do that.
+
+## The mechanism
+
+FieldWorks's `HCLoader.LoadEndoCompoundingRule` looks up a per-rule maximum application count from a
+separate `parserParams` XML document (not the main HC grammar XML), keyed by the compound rule's
+GUID:
+
+```csharp
+int maxApps = 1;
+if (m_CompoundRuleLookup.TryGetValue(compoundRule.Guid.ToString(), out maxApps))
+ hcCompoundRule.MaxApplicationCount = maxApps;
+```
+
+(`HCLoader.cs:1894-1896`), populated earlier from a `` element's `maxApps` attributes
+(`HCLoader.cs:104-111`). `LoadExoCompoundingRule` (`HCLoader.cs:1922` onward) has no equivalent
+lookup anywhere in its body — every `CompoundingRule` it builds keeps the C# constructor's default of
+`MaxApplicationCount = 1`, with no code path that could raise it. An exocentric compound rule (the
+kind that produces a category not inherited from either the head or non-head component, e.g. a
+noun-noun compound that becomes a distinct new nominal category) can therefore never recurse more
+than once per derivation in a FieldWorks-generated grammar, regardless of any setting a grammar
+author might look for.
+
+## Why this is easy to miss
+
+The cap is invisible from the main HC grammar XML — `MaxApplicationCount` shows up on the
+`CompoundingRule` element the same way it would for any other rule, and a reader checking "did the
+author configure this to recurse" has to know to look in a *different*, parser-parameters document
+(the one `m_CompoundRuleLookup` is built from) rather than the grammar itself, and to know that this
+lookup is only ever consulted for endocentric rules. A grammar author who wants three-way (or deeper)
+compounding, and who successfully configures a higher `maxApps` for an endocentric rule, can
+reasonably expect the same configuration surface to work for an exocentric rule modeling the same
+kind of recursive compounding — it does not, because the loading code for exocentric rules never
+reads that lookup at all.
+
+## Concrete example
+
+A language allows noun compounds of arbitrary length (`N N N ... N`), modeled as one exocentric
+compounding rule (the output category isn't simply "the same as the head," so it's modeled as
+exocentric) meant to apply repeatedly, left-associatively, to build up longer compounds one pair at a
+time. Because `LoadExoCompoundingRule` never overrides `MaxApplicationCount`, the compiled rule stays
+capped at `1` — only genuine two-element compounds are generable or parseable; any three-or-more-noun
+compound the grammar author expected to derive by reapplying the same rule silently fails to parse or
+generate, with no configuration surface (short of switching the rule to an endocentric model, if the
+category semantics allow it) to fix it.
+
+## Fix
+
+If a compounding pattern needs to recurse more than once per derivation, model it as an *endocentric*
+compound rule (where the output category is inherited from the head) so FieldWorks's `maxApps`
+lookup applies, and configure that count explicitly in the parser-parameters settings rather than
+assuming the default is unlimited or that it matches whatever an affix rule's default would be. For a
+genuinely exocentric recursive-compounding pattern, there is currently no configuration surface in
+the FieldWorks-to-HC compiler to raise the cap above `1` — the underlying `CompoundingRule.MaxApplicationCount`
+property itself has no such restriction (a hand-edited HC XML grammar can set it directly), only the
+FieldWorks loader path for exocentric rules never populates it from anything but the default.
diff --git a/Docs/ai-parser-help/broken/coocurrence-rule-requires-all-not-any.md b/Docs/ai-parser-help/broken/coocurrence-rule-requires-all-not-any.md
new file mode 100644
index 0000000000..f444435276
--- /dev/null
+++ b/Docs/ai-parser-help/broken/coocurrence-rule-requires-all-not-any.md
@@ -0,0 +1,81 @@
+---
+title: "A multi-morpheme co-occurrence exclusion requires all listed morphemes together — not any one of them"
+implements: src/SIL.Machine.Morphology.HermitCrab/MorphCoOccurrenceRule.cs, src/SIL.Machine.Morphology.HermitCrab/Allomorph.cs
+category: morphotactics
+symptom: wrong-parse
+grammar_visible: partially
+---
+
+## What it is
+
+FLEx's "ad hoc co-occurrence" mechanism lets a grammar author pick a key morpheme (or allomorph) and
+list one or more *other* morphemes/allomorphs that must or must not co-occur with it
+(`IMoMorphAdhocProhib`/`IMoAlloAdhocProhib` in FieldWorks, compiled to
+`MorphemeCoOccurrenceRule`/`AllomorphCoOccurrenceRule` in the HC engine). When that "others" list has
+more than one entry, a grammar author modeling "the key morpheme must not co-occur with A, or with B,
+or with C" (three separate, independent exclusions) by listing A, B, and C in one rule gets something
+much weaker: the exclusion only fires when **all** of A, B, and C appear together with the key
+morpheme in the same word. Any word containing the key morpheme plus only one or two of the listed
+others is not excluded at all.
+
+## The mechanism
+
+`MorphCoOccurrenceRule.IsWordValid` (`MorphCoOccurrenceRule.cs:82-87`) is:
+
+```csharp
+public bool IsWordValid(T key, Word word)
+{
+ if (_type == ConstraintType.Exclude)
+ return !CoOccurs(key, word);
+ return CoOccurs(key, word);
+}
+```
+
+and `CoOccurs` (`MorphCoOccurrenceRule.cs:92-170`) walks the word's morphs (in the adjacency order
+the rule specifies — `Anywhere`, `SomewhereToLeft/Right`, or `AdjacentToLeft/Right`) removing each
+`others` entry from a working copy of the list as it's matched, and returns `others.Count == 0` at
+the end — true only if **every** entry in `_others` was found. For `ConstraintType.Exclude`,
+`IsWordValid` negates that: the word is invalid only when `CoOccurs` returns true, i.e. only when
+*all* of the listed morphemes were present together with the key. If the list has three entries and
+a word contains the key plus only one of them, `CoOccurs` returns false (not every entry was matched),
+so `IsWordValid` (Exclude) returns `true` — the word is accepted, not rejected.
+
+This is not a quirk of how the rule happens to get built from a single-item FieldWorks list — it's
+the same class regardless of list length, and FieldWorks does build multi-item lists directly into
+one rule's `others` set. `HCLoader.LoadAllomorphCoOccurrenceRules`/`LoadMorphemeCoOccurrenceRules`
+each take one `IMoAlloAdhocProhib`/`IMoMorphAdhocProhib`'s entire `RestOfAllosRS`/`RestOfMorphsRS`
+reference collection (FieldWorks's "Rest of Allomorphs"/"Rest of Morphemes" field) and pass it as the
+`others` list to a single `new AllomorphCoOccurrenceRule(ConstraintType.Exclude, others, adjacency)`/
+`new MorphemeCoOccurrenceRule(...)` call — one rule per adhoc-prohibition entry, not one rule per
+listed morpheme (`HCLoader.cs:2163-2239`). A FieldWorks user who adds three morphemes to one
+prohibition's "Rest of" field, expecting three independent exclusions, gets one rule whose `others`
+list has three entries and whose `Exclude` semantics only fire on their joint co-occurrence.
+
+## Why this is easy to miss
+
+The FLEx UI field is a plural reference list ("Rest of Allomorphs"), which reads naturally as "any of
+these" when the grammar author is modeling several independent things the key morpheme shouldn't
+combine with — the field's own name doesn't distinguish "all of these together" from "any one of
+these." Nothing in the exported HC XML makes the distinction more obvious either: the rule just lists
+several morpheme IDs, and a reader has to already know `MorphCoOccurrenceRule`'s all-or-nothing
+matching semantics to recognize that a three-item exclusion list is far weaker than three separate
+one-item lists would be.
+
+## Concrete example
+
+A grammar wants to say "the passive suffix `-en` cannot co-occur with any of the three
+object-agreement suffixes `-a`, `-i`, `-u`" — three independent, pairwise exclusions. Modeled as one
+`IMoMorphAdhocProhib` with `FirstMorphemeRA = -en` and `RestOfMorphsRS = {-a, -i, -u}`, the compiled
+rule's `others` list is `[-a, -i, -u]`, and `CoOccurs` only returns true (triggering the `Exclude`)
+when a word contains `-en` together with `-a` *and* `-i` *and* `-u` all at once — a combination that
+may never even be otherwise derivable. Any word with `-en` plus just `-a` (the actually-intended,
+common case to reject) passes this rule with no complaint, because two of the three required
+`others` entries are missing from that particular word.
+
+## Fix
+
+Model each independent pairwise (or n-ary "must not co-occur with this specific one") exclusion as
+its own separate ad hoc prohibition entry, with exactly one morpheme/allomorph in its "Rest
+of"/`RestOfAllosRS`/`RestOfMorphsRS` list. Only put more than one entry in a single prohibition's
+"Rest of" list when the intent genuinely is "excluded only when all of these co-occur together" —
+that is the only semantics `MorphCoOccurrenceRule` gives a multi-item list.
diff --git a/Docs/ai-parser-help/broken/inflection-class-no-default-silent-gating.md b/Docs/ai-parser-help/broken/inflection-class-no-default-silent-gating.md
new file mode 100644
index 0000000000..289e233ca6
--- /dev/null
+++ b/Docs/ai-parser-help/broken/inflection-class-no-default-silent-gating.md
@@ -0,0 +1,75 @@
+---
+title: "A stem with no inflection class and no configured default silently fails every class-restricted rule"
+implements: src/SIL.Machine.Morphology.HermitCrab/MprFeatureSet.cs, FieldWorks Src/LexText/ParserCore/HCLoader.cs
+category: feature-system
+symptom: missing-parse
+grammar_visible: partially
+---
+
+## What it is
+
+FieldWorks lets a grammar author set a "default inflection class" for a part of speech; H. Andrew
+Black's conceptual intro (around the discussion of default inflection class, roughly line 1224 in
+the full-text source) states that when this default is configured, "the FieldWorks Language Explorer
+parser will use this default inflection class for any stem that is not overtly tagged" — implying
+that when no default is configured, an untagged stem gets nothing, silently. FieldWorks's HC loader
+confirms exactly this fallback chain, and the HC engine's MPR-feature matching confirms exactly what
+happens downstream when it comes up empty.
+
+## The mechanism
+
+FieldWorks's loader resolves a stem's inflection class with:
+
+```csharp
+protected static IMoInflClass GetInflClass(IMoStemMsa msa)
+{
+ if (msa.InflectionClassRA != null)
+ return msa.InflectionClassRA;
+ if (msa.PartOfSpeechRA != null)
+ return GetDefaultInflClass(msa.PartOfSpeechRA);
+ return null;
+}
+```
+
+walking up the part-of-speech hierarchy for a configured `DefaultInflectionClassRA` if the stem
+itself isn't tagged, and returning `null` if neither the stem nor any ancestor POS has one. When
+`GetInflClass` returns `null`, the corresponding `MprFeatures.Add(...)` call for inflection class is
+simply skipped — the compiled HC lexical entry gets **no** inflection-class MPR feature at all, not
+a "default" placeholder feature.
+
+On the engine side, a rule restricted to specific inflection classes expresses that via
+`RequiredMprFeatures`, checked through `MprFeatureSet.IsMatchRequired`
+(`MprFeatureSet.cs:46-70`): for an ungrouped required feature (or a group with `MatchType.All`), the
+check is `group.Any(mf => !mprFeats.Contains(mf))` — if the stem's compiled MPR-feature set doesn't
+contain the required class feature at all (because it was never added), the match fails
+unconditionally. There is no "unmarked stems match everything" or "unmarked stems get treated as the
+elsewhere case" behavior anywhere in this check — a stem with no inflection-class feature simply
+fails every rule gated on any inflection class.
+
+## Why this is easy to miss
+
+Nothing in the grammar's rule XML says "this rule requires the stem to have been assigned an
+inflection class" in those words — it just lists `RequiredMprFeatures` referencing specific class
+IDs, which reads the same whether or not every stem in the lexicon is guaranteed to carry one of
+them. A grammar author who sets up inflection classes for the *irregular* subset of a POS and assumes
+"everything else falls through to some sensible default" gets that behavior only if they remembered
+to configure `DefaultInflectionClassRA` on the POS (or an ancestor POS) — if they didn't, every stem
+they didn't explicitly tag silently fails every class-restricted rule, with no trace signal beyond an
+ordinary MPR-feature mismatch that looks identical to "this stem really is the wrong class."
+
+## Concrete example
+
+A POS `posN` has inflection classes `classI` (a handful of irregular nouns, explicitly tagged) and no
+configured default. A plural-suffix rule requires `classI` OR requires "not classI" via
+`ExcludedMprFeatures`, depending on how the two paradigms were modeled; either way, the majority of
+`posN` stems were never tagged with any inflection class at all (the author assumed "untagged = the
+regular pattern"). Every one of those untagged stems fails the class-gated rule that was supposed to
+be their regular paradigm, because their compiled MPR-feature set has no inflection-class feature to
+match against — not because they matched the wrong class, but because they matched no class.
+
+## Fix
+
+Either configure an explicit default inflection class on the POS (or the relevant ancestor POS) so
+every untagged stem actually receives a class feature, or design class-restricted rules so the
+"regular"/default paradigm's rule has no inflection-class requirement at all (only the irregular
+classes are gated), so an absent class feature can't accidentally exclude the majority case.
diff --git a/Docs/ai-parser-help/broken/lexfamily-blocking-generation-only.md b/Docs/ai-parser-help/broken/lexfamily-blocking-generation-only.md
new file mode 100644
index 0000000000..c2d4705d54
--- /dev/null
+++ b/Docs/ai-parser-help/broken/lexfamily-blocking-generation-only.md
@@ -0,0 +1,77 @@
+---
+title: "LexFamily suppletion blocking silently discards a regular derivation, and only during generation"
+implements: src/SIL.Machine.Morphology.HermitCrab/Word.cs, src/SIL.Machine.Morphology.HermitCrab/LexFamily.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisCompoundingRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisRealizationalAffixProcessRule.cs
+category: lexicon
+symptom: missing-parse
+grammar_visible: partially
+---
+
+## What it is
+
+HermitCrab supports "blocking": when a regularly-derived word would be replaced by a suppletive
+family member's own irregular form (classic "*goed* is blocked by *went*"). This mechanism is
+controlled by `AffixProcessRule.Blockable` / `RealizationalAffixProcessRule.Blockable` /
+`CompoundingRule` (each initialized to `true` in its constructor — e.g.
+`MorphologicalRules/AffixProcessRule.cs:29`). It has no corresponding check on the analysis side at
+all, and its trigger condition is a feature-structure subsumption test that's easy to get broader
+than intended.
+
+## The mechanism
+
+After a rule derives a word, `Word.CheckBlocking` (`Word.cs:472-497`) runs:
+
+```csharp
+LexFamily family = ((LexEntry)RootAllomorph.Morpheme).Family;
+if (family == null) return false;
+foreach (LexEntry entry in family.Entries)
+{
+ if (entry != RootAllomorph.Morpheme
+ && entry.Stratum == Stratum
+ && SyntacticFeatureStruct.Subsumes(entry.SyntacticFeatureStruct))
+ {
+ word = new Word(entry.PrimaryAllomorph, RealizationalFeatureStruct.Clone()) { ... };
+ return true;
+ }
+}
+```
+
+If the just-derived word's own feature structure `Subsumes` some other family member's feature
+structure, the derived word is discarded outright and replaced by that other entry's primary
+allomorph. This is called from `SynthesisAffixProcessRule.cs:198`,
+`SynthesisCompoundingRule.cs:192`, and `SynthesisRealizationalAffixProcessRule.cs:127`, each gated
+by `if (_rule.Blockable && outWord.CheckBlocking(out Word newWord))` — and `Blockable` defaults to
+`true` for every rule type unless a grammar author explicitly turns it off.
+
+Two things make this a correctness trap rather than just "suppletion working as intended":
+
+1. **It's synthesis-only.** There is no equivalent check anywhere in `AnalysisAffixProcessRule.cs`,
+ `AnalysisCompoundingRule.cs`, or `LexEntry.cs`/`LexFamily.cs`. Blocking suppresses a candidate
+ during generation but has no bearing on what parses during analysis — the two directions are not
+ symmetric for a family with a suppletive member.
+2. **Subsumption, not equality, decides it.** `Subsumes` succeeds whenever the derived word's
+ feature structure is *at least as specific as* the family member's — so a family member with a
+ deliberately broad or under-specified feature structure (e.g. left with an unassigned feature
+ that was meant to narrow it to one paradigm cell) can end up blocking derivations well beyond the
+ single irregular cell the grammar author intended to model.
+
+## Concrete example
+
+A `LexFamily` groups `go` (regular root, produces `go+ed` via a regular past-tense
+`AffixProcessRule`) and `went` (irregular root, `partOfSpeech=posV`, own feature structure just
+`{tense=past}`, no other features assigned). Both entries share a stratum. Any regularly-derived
+past-tense form of `go` — whatever its full feature structure ends up being — subsumes `went`'s bare
+`{tense=past}` requirement (an unspecified feature structure is a subsumer of anything more specific
+in HC's feature system), so `Word.CheckBlocking` fires on every one of them: the regular
+`go`+`-ed` derivation is silently discarded and replaced by `went` every single time, not just for
+the one paradigm cell the author meant to override.
+
+## Fix
+
+- Set `blockable="false"` on a rule if you want its output to survive regardless of family
+ suppletion.
+- If suppletion should only block one specific paradigm cell, give the irregular family member's
+ own feature structure exactly that cell's features (not a bare/under-specified structure) so
+ `Subsumes` can't match broader derivations than intended.
+- Do not assume blocking constrains parsing — a form that blocking would suppress during generation
+ can still be accepted as a valid analysis when parsing the same surface string, because
+ `CheckBlocking` never runs on the analysis side.
diff --git a/Docs/ai-parser-help/broken/mpr-group-matchtype-default-is-any.md b/Docs/ai-parser-help/broken/mpr-group-matchtype-default-is-any.md
new file mode 100644
index 0000000000..82b7532817
--- /dev/null
+++ b/Docs/ai-parser-help/broken/mpr-group-matchtype-default-is-any.md
@@ -0,0 +1,93 @@
+---
+title: "An MPR feature group's match type defaults to Any (OR), not All — the opposite of what a gating author might assume"
+implements: src/SIL.Machine.Morphology.HermitCrab/MprFeatureGroup.cs, src/SIL.Machine.Morphology.HermitCrab/MprFeatureSet.cs, src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs, src/SIL.Machine.Morphology.HermitCrab/HermitCrabInput.dtd
+category: feature-system
+symptom: silent-misconfiguration
+grammar_visible: partially
+---
+
+## What it is
+
+`MprFeatureGroup.MatchType` (`MprFeatureGroup.cs:73`) is an `MprFeatureGroupMatchType` with values
+`Any` ("when any features match within the group") and `All` ("only if all features match within the
+group") (`MprFeatureGroup.cs:10-21`). When a grammar's `MorphologicalPhonologicalRuleFeatureGroup`
+XML omits the `matchType` attribute, the loader — and the format's own DTD — both default it to
+`Any`, not `All`. A grammar author reasoning "an unmarked/omitted setting should be the strict,
+conjunctive one" gets the loose, disjunctive one instead, silently.
+
+## The mechanism
+
+The DTD itself states the default explicitly:
+
+```
+
+```
+
+(`HermitCrabInput.dtd:82-87`). The C# loader's fallback agrees:
+
+```csharp
+private static MprFeatureGroupMatchType GetGroupMatchType(string matchTypeStr)
+{
+ switch (matchTypeStr)
+ {
+ case "all": return MprFeatureGroupMatchType.All;
+ case "any": return MprFeatureGroupMatchType.Any;
+ }
+ return MprFeatureGroupMatchType.Any;
+}
+```
+
+(`XmlLanguageLoader.cs:95-106`) — any omitted, empty, or unrecognized `matchType` string falls
+through to `Any`. This matches the enum's own declared order (`Any` is the first, zero-valued
+member), so even a hypothetical uninitialized `MprFeatureGroup` would default the same way.
+
+The consequence at match time, `MprFeatureSet.IsMatchRequired` (`MprFeatureSet.cs:46-70`):
+
+```csharp
+if (group.Key == null || group.Key.MatchType == MprFeatureGroupMatchType.All)
+{
+ if (group.Any(mf => !mprFeats.Contains(mf))) { mismatchGroup = group.Key; return false; }
+}
+else // Any
+{
+ if (group.All(mf => !mprFeats.Contains(mf))) { mismatchGroup = group.Key; return false; }
+}
+```
+
+For an `All`-type group, the check fails as soon as *any one* referenced feature is missing from the
+word's accumulated set — every listed feature must be present. For an `Any`-type group (the default),
+the check only fails if *every* referenced feature is missing — a single one present is enough to
+satisfy the whole group. (Note this also means an MPR feature with no group at all — `group.Key ==
+null` — is always treated with `All` semantics, i.e. as its own singleton conjunctive requirement;
+the `Any` default only applies to features that are actually placed in a declared group.)
+
+## Why this is the opposite trap from the intuitive one
+
+It would be easy to assume the risk runs the other way — that a grammar author wanting OR semantics
+("any one of these MPR features suffices to license this rule") has to opt in and might forget to,
+silently landing on stricter AND gating instead. The verified default runs the other direction: the
+default is already `Any`. The actual silent-misconfiguration risk is for an author who *wants*
+conjunctive gating — "all of these MPR features must be present together for this rule to apply" —
+and doesn't realize that leaving `matchType` unset does not give them that. Any single feature in the
+group being present is enough, which can silently *under*-constrain a rule that was meant to require
+several co-occurring MPR features at once.
+
+## Concrete example
+
+A grammar groups MPR features `hasPrefixA` and `hasPrefixB` into one group meant to gate a rule that
+should only apply when a stem carries *both* prefixes' tags (e.g. a portmanteau-blocking condition).
+The group's XML omits `matchType`. Because the default is `Any`, a stem carrying only `hasPrefixA`
+(not `hasPrefixB`) already satisfies `RequiredMprFeatures` for that group — the rule applies to stems
+the author only meant to license when both tags co-occurred.
+
+## Fix
+
+Set `matchType="all"` explicitly whenever a group is meant to require every one of its features
+together — do not rely on the omitted-attribute default, which is `any`. Conversely, if `any`
+(OR) semantics really is what you want, it's already the default and you don't need to state it, but
+stating it explicitly still makes the grammar's intent legible to the next person reading the XML.
diff --git a/Docs/ai-parser-help/broken/mpr-group-output-default-overwrite.md b/Docs/ai-parser-help/broken/mpr-group-output-default-overwrite.md
new file mode 100644
index 0000000000..583bce52b6
--- /dev/null
+++ b/Docs/ai-parser-help/broken/mpr-group-output-default-overwrite.md
@@ -0,0 +1,84 @@
+---
+title: "An MPR feature group's output type defaults to Overwrite, not Append"
+implements: src/SIL.Machine.Morphology.HermitCrab/MprFeatureGroup.cs, src/SIL.Machine.Morphology.HermitCrab/MprFeatureSet.cs, src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs, src/SIL.Machine.Morphology.HermitCrab/HermitCrabInput.dtd
+category: feature-system
+symptom: silent-misconfiguration
+grammar_visible: partially
+---
+
+## What it is
+
+`MprFeatureGroup.Output` (`MprFeatureGroup.cs:79`) is an `MprFeatureGroupOutput` with values
+`Overwrite` ("overwrites all existing features in the same group") and `Append` ("appends
+features") (`MprFeatureGroup.cs:26-37`). When a group's XML omits the `outputType` attribute, both
+the DTD and the C# loader default it to `Overwrite`, not `Append` — the non-monotone, order-dependent
+behavior described in `speed/mpr-overwrite-order-dependence.md` is what an omitted attribute actually
+gets, not the safer accumulating behavior a reader of that file's own wording ("declared with output
+policy Overwrite instead of the default Append") might assume the loader falls back to.
+
+## The mechanism
+
+The DTD states the default explicitly:
+
+```
+
+```
+
+(`HermitCrabInput.dtd:82-87`). The loader's fallback agrees:
+
+```csharp
+private static MprFeatureGroupOutput GetGroupOutput(string outputTypeStr)
+{
+ switch (outputTypeStr)
+ {
+ case "overwrite": return MprFeatureGroupOutput.Overwrite;
+ case "append": return MprFeatureGroupOutput.Append;
+ }
+ return MprFeatureGroupOutput.Overwrite;
+}
+```
+
+(`XmlLanguageLoader.cs:108-119`) — an omitted, empty, or unrecognized `outputType` string falls
+through to `Overwrite`. That default feeds directly into `MprFeatureSet.AddOutput`
+(`MprFeatureSet.cs:29-44`), which is what every rule application runs on its output MPR features
+(both ordinary affix-process allomorphs and compounding subrules): for a group whose `Output` is
+`Overwrite`, applying a new rule's output silently drops any of that group's features the word
+already carried unless the new output restates them — "last rule to touch this group wins," not
+accumulation.
+
+## Why this is easy to miss
+
+A grammar author who reasons "MPR features are just tags, and tags accumulate unless I say
+otherwise" gets the *opposite* of that reasonable assumption the moment they define a
+`MorphologicalPhonologicalRuleFeatureGroup` and don't set `outputType` — which is the common case,
+since `outputType` only matters once a grammar author is deliberately grouping features, at which
+point `Append` (the semantically "boring," accumulating choice) looks like it should need no
+attribute at all. Nothing about the group's own declaration signals this; the non-monotone behavior
+only becomes visible once two different rules in a derivation touch the same group and a grammar
+author notices one rule's tag vanished from the word's final state. See
+`speed/mpr-overwrite-order-dependence.md` for the full consequences of `Overwrite` semantics once
+they're in effect (order-dependence, and its interaction with the engine's ability to collapse
+otherwise-equivalent candidate derivations) — this gotcha is specifically about how easy it is to end
+up with `Overwrite` unintentionally, by omission, rather than the mechanism's downstream effects.
+
+## Concrete example
+
+A grammar groups MPR features `tagX` and `tagY` under one `MorphologicalPhonologicalRuleFeatureGroup`
+with no `outputType` attribute, intending them as independent accumulating flags checked later by an
+`ExcludedMprFeatures` gate elsewhere. Rule A applies first and outputs `tagX`; rule B applies later
+in the same derivation and outputs `tagY`. Because the group defaults to `Overwrite`, rule B's
+application removes `tagX` from the word's MPR-feature set before adding `tagY` — the final word
+carries only `tagY`, even though both rules fired. A downstream gate checking for `tagX` fails
+silently, exactly as if rule A had never applied.
+
+## Fix
+
+Set `outputType="append"` explicitly on any `MorphologicalPhonologicalRuleFeatureGroup` whose
+features are meant to accumulate across a derivation. Only rely on the omitted-attribute default when
+"last rule to touch this group wins" is the actually-intended semantics (e.g. a group modeling a
+paradigm cell that a later derivation step is meant to reset outright).
diff --git a/Docs/ai-parser-help/broken/null-affix-cannot-express-default.md b/Docs/ai-parser-help/broken/null-affix-cannot-express-default.md
new file mode 100644
index 0000000000..7a946fd1fb
--- /dev/null
+++ b/Docs/ai-parser-help/broken/null-affix-cannot-express-default.md
@@ -0,0 +1,82 @@
+---
+title: "A null/zero-realization affix is a real rule application, not a free default value"
+implements: src/SIL.Machine.Morphology.HermitCrab/AffixTemplateSlot.cs, src/SIL.Machine.Morphology.HermitCrab/SynthesisAffixTemplateRule.cs
+category: morphotactics
+symptom: wrong-parse
+grammar_visible: yes
+---
+
+## What it is
+
+H. Andrew Black's FLEx/HC conceptual intro (§2.1.2.2, "Optional affix slots") points out that some
+categories have a "default" value realized by zero marking (e.g. singular number, unmarked when
+plural is overtly suffixed) and states plainly that "the current parsers do not allow us to mark
+such default features" — a null/zero affix is not the same thing as an actual default. This is
+verifiable directly in the engine's slot-optionality logic.
+
+## The mechanism
+
+`AffixTemplateSlot.Optional` only auto-derives `true` when the slot has **no rules at all**:
+
+```csharp
+public bool Optional
+{
+ get
+ {
+ if (_rules.Count == 0)
+ return true;
+ return _isOptional;
+ }
+ set { _isOptional = value; }
+}
+```
+
+(`AffixTemplateSlot.cs:35-45`). A slot holding one rule — even a rule whose only allomorph spells
+out nothing overt (a "null suffix") — is **not** automatically optional; its `Optional` flag is
+whatever the grammar author set explicitly, and if the author left it non-optional (the natural
+choice for "this category is always marked, sometimes by zero"), `SynthesisAffixTemplateRule.ApplySlots`
+requires that rule to actually apply, successfully, before the derivation can proceed past that slot:
+
+```csharp
+foreach (Word outWord in _rules[i].Apply(input))
+ ApplySlots(outWord, i + 1, output);
+if (!_template.Slots[i].Optional)
+ return; // no successful application at this (mandatory) slot -> dead end
+```
+
+(`SynthesisAffixTemplateRule.cs:39-49`). A null-realization rule still goes through the same
+application machinery as any other affix rule — its `RequiredSyntacticFeatureStruct` must unify, any
+`RequiredMprFeatures`/environment/stem-name gates on it must be satisfied, and so on. If the
+conditions that license the null affix's rule aren't met for some input, the mandatory slot has
+nothing to apply and the whole derivation dead-ends at that slot — it does not fall through to "just
+assume the default value," because there is no such fallback in the engine.
+
+## Why this is easy to miss
+
+The zero-marking case *looks* like exactly what a default feature value should be: nothing is
+written, nothing seems constrained. But because the null affix is implemented as an ordinary rule
+occupying a non-optional slot, every one of that rule's own gates (required features, MPR features,
+environment) still has to pass. A form that should trivially take "the default" because none of the
+overt alternatives apply can instead fail to derive at all, if the null rule's own required
+conditions don't happen to be satisfied for that particular stem — there is no separate "otherwise
+assume singular" fallback path.
+
+## Concrete example
+
+A noun category has number marked by `-s` (plural) vs. a null suffix (singular), both rules placed
+in one non-optional template slot. If the null-suffix rule is written with its own
+`RequiredSyntacticFeatureStruct` (e.g. requiring `num=sg` to already be set elsewhere) rather than as
+the true, condition-free elsewhere case, any noun stem for which `num` hasn't already been
+independently assigned by an earlier rule fails at this slot entirely — neither the `-s` rule nor the
+null rule can apply, and the whole derivation dies, when the grammar author's mental model was "no
+suffix just means singular by default."
+
+## Fix
+
+If a category's "default" is a zero realization, still model it as a real, unconditioned rule (no
+`requiredSyntacticFeatureStruct` beyond the bare POS, no MPR-feature gate that could fail) so it acts
+as a true elsewhere case within its slot — or, per Black's suggested workaround, mark the slot
+optional and have every overt affix *set* the non-default feature value explicitly, leaving the
+feature unset (rather than null-affixed) when the slot doesn't fire, and have downstream rules or
+default unification handle the "otherwise assume the default" logic instead of relying on the
+template slot mechanism to express it.
diff --git a/Docs/ai-parser-help/broken/realizational-rule-cannot-add-features.md b/Docs/ai-parser-help/broken/realizational-rule-cannot-add-features.md
new file mode 100644
index 0000000000..20aa713f9a
--- /dev/null
+++ b/Docs/ai-parser-help/broken/realizational-rule-cannot-add-features.md
@@ -0,0 +1,81 @@
+---
+title: "RealizationalRule can only spell out features already present — it cannot assign new ones the way AffixProcessRule can"
+implements: src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/RealizationalAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisRealizationalAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/HermitCrabInput.dtd
+category: morphotactics
+symptom: silent-misconfiguration
+grammar_visible: yes
+---
+
+## What it is
+
+`AffixProcessRule` and `RealizationalAffixProcessRule` look like two flavors of the same thing (both
+compile from a ``-family element, both hold a list of `AffixProcessAllomorph`
+subrules, both apply through a Synthesis/Analysis rule pair), but they differ in a way that isn't
+obvious from either the C# API surface or a first read of the DTD: `RealizationalRule` has no
+mechanism at all for introducing a new syntactic feature or part of speech into the word. It can only
+"realize" (spell out morphologically) a feature bundle the word already carries.
+
+## The mechanism
+
+`AffixProcessRule` has `OutSyntacticFeatureStruct` (`MorphologicalRules/AffixProcessRule.cs:64`),
+populated on synthesis via `outWord.SyntacticFeatureStruct.PriorityUnion(_rule.OutSyntacticFeatureStruct)`
+(`SynthesisAffixProcessRule.cs:182`) — this is the mechanism an ordinary inflectional or derivational
+affix uses to introduce a feature value (or change part of speech) that wasn't there before.
+
+`RealizationalAffixProcessRule` has no equivalent property. Its DTD element confirms this is not an
+oversight in the C# class alone:
+
+```
+
+```
+
+(`HermitCrabInput.dtd:362`) — there is no `outputPartOfSpeech` attribute and no `OutputHeadFeatures`
+child element, unlike the ordinary `MorphologicalRule` element. What it has instead is
+`RealizationalFeatures`, loaded into `RealizationalFeatureStruct`
+(`RealizationalAffixProcessRule.cs:54`), which `SynthesisRealizationalAffixProcessRule.Apply` merges
+via `outWord.SyntacticFeatureStruct.PriorityUnion(_rule.RealizationalFeatureStruct)`
+(`SynthesisRealizationalAffixProcessRule.cs:122`) — syntactically this looks like the same kind of
+merge an `AffixProcessRule` does, but semantically it is gated very differently: the rule first
+requires `_rule.RealizationalFeatureStruct.Subsumes(input.RealizationalFeatureStruct)`
+(`SynthesisRealizationalAffixProcessRule.cs:46`) and then refuses to apply at all if
+`IsBlocked(_rule.RealizationalFeatureStruct, input.SyntacticFeatureStruct, ...)` finds every one of
+the rule's realizational features already present in the word's accumulated syntactic feature
+structure (`SynthesisRealizationalAffixProcessRule.cs:49-59, 168-195`). A `RealizationalRule` is
+designed to spell out a feature bundle some other mechanism (typically an inflectional
+`AffixProcessRule`'s `OutputHeadFeatures`, or `InflFeatsOA` on the MSA in the FieldWorks compiler) has
+already assigned abstractly — not to be the thing that assigns it.
+
+## Why this is easy to miss
+
+Both rule types are exposed in FieldWorks as morphological-rule-like entries with a features section
+in the UI, and both end up merging a feature structure into the word during synthesis via what reads,
+in the compiled C# alone, like the same `PriorityUnion` idiom. A grammar author modeling an agreement
+affix as a `RealizationalRule` because "it realizes agreement features" can reasonably expect it to
+also be able to introduce a feature the word didn't have yet (e.g. assigning `case=nom` for the first
+time) the way an ordinary affix would — but there is no attribute or element in the `RealizationalRule`
+schema that does that, and no code path in `SynthesisRealizationalAffixProcessRule` that adds a
+feature the accumulated structure doesn't already carry in some form. The rule will still compile and
+load without error; it simply never introduces the feature the author expected, and unification against
+`RequiredHeadFeatures`/`RequiredFootFeatures` downstream continues to fail silently for words that
+were supposed to receive it from this rule.
+
+## Concrete example
+
+A grammar models subject-agreement suffixes as `RealizationalRule`s, expecting each suffix to assign
+the relevant person/number combination to a word that previously had no agreement features at all
+(e.g. a bare verb stem). Because `RealizationalRule` has no output-feature mechanism, the suffix rule
+requires `RequiredHeadFeatures` to already be compatible and merges only `RealizationalFeatureStruct`
+— which, per `IsBlocked`, only proceeds when the target features are not already fully present, but
+also never actually assigns them if nothing upstream (e.g. an inflectional `AffixProcessRule`'s
+`OutputHeadFeatures`) already put an abstract agreement feature bundle onto the word first. A word
+that never went through such an upstream rule never receives any agreement value from the
+realizational suffix, no matter how many realizational rules are chained.
+
+## Fix
+
+Use `RealizationalRule` only to spell out features an inflectional `AffixProcessRule` (or another
+mechanism) has already assigned abstractly elsewhere in the same derivation — never as the sole
+mechanism responsible for introducing a feature value. If an affix genuinely needs to introduce a new
+syntactic feature or part of speech, model it as an ordinary `AffixProcessRule` with
+`OutputHeadFeatures`/`outputPartOfSpeech`, not as a `RealizationalRule`.
diff --git a/Docs/ai-parser-help/broken/realizational-rule-no-application-cap.md b/Docs/ai-parser-help/broken/realizational-rule-no-application-cap.md
new file mode 100644
index 0000000000..3d3f0de522
--- /dev/null
+++ b/Docs/ai-parser-help/broken/realizational-rule-no-application-cap.md
@@ -0,0 +1,73 @@
+---
+title: "RealizationalAffixProcessRule has no MaxApplicationCount backstop, unlike AffixProcessRule"
+implements: src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/RealizationalAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisRealizationalAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisRealizationalAffixProcessRule.cs
+category: morphotactics
+symptom: wrong-parse
+grammar_visible: partially
+---
+
+## What it is
+
+An ordinary `AffixProcessRule` is capped at one application per derivation by default —
+`MaxApplicationCount = 1` is set in its constructor (`MorphologicalRules/AffixProcessRule.cs:28`)
+and enforced on every application: `if (input.GetApplicationCount(_rule) >= _rule.MaxApplicationCount)`
+(`MorphologicalRules/SynthesisAffixProcessRule.cs:46`, mirrored on the analysis side at
+`AnalysisAffixProcessRule.cs:45`). `RealizationalAffixProcessRule` — the rule type meant for pure
+feature-realization/spellout rules rather than form-changing affixation — has no such property at
+all. Reading the whole file confirms it: no `MaxApplicationCount` field, no constructor default, no
+check anywhere in `RealizationalAffixProcessRule.cs`, `SynthesisRealizationalAffixProcessRule.cs`, or
+`AnalysisRealizationalAffixProcessRule.cs`.
+
+## The mechanism
+
+The only guard against a `RealizationalAffixProcessRule` reapplying itself indefinitely in the same
+derivation is a feature-based recursive check, `IsBlocked`
+(`SynthesisRealizationalAffixProcessRule.cs:168-195`), gated by:
+
+```csharp
+if (!_rule.RealizationalFeatureStruct.IsEmpty
+ && IsBlocked(_rule.RealizationalFeatureStruct, input.SyntacticFeatureStruct, ...))
+```
+
+`IsBlocked` walks the rule's `RealizationalFeatureStruct` recursively and returns true only when
+every one of the rule's realizational features is *already present* in the word's accumulated
+syntactic feature structure. If a rule's realizational features are only a partial or ambiguous
+subset of what's already assigned — or if the rule sets a feature that the accumulated structure
+doesn't yet carry in a form `IsBlocked` recognizes as "already there" — this guard does not fire, and
+nothing else stops the rule from applying again in a cyclic derivation. Unlike an
+`AffixProcessRule`, there is no numeric backstop underneath the feature check.
+
+## Why this is easy to miss
+
+A grammar author who reasons "every morphological rule type gets a default `MaxApplicationCount` of
+1 unless I explicitly raise it" (true for `AffixProcessRule` and `CompoundingRule`, whose
+constructors both set `MaxApplicationCount = 1`, e.g. `MorphologicalRules/CompoundingRule.cs:19`)
+will not find that assumption holds for `RealizationalAffixProcessRule` at all — there is no
+attribute to raise or lower, because the rule type doesn't have the property. Nothing in a grammar's
+XML for a `RealizationalRule` (which has no `multipleApplication` attribute in the schema for this
+rule kind) hints that the usual per-rule application cap doesn't exist here.
+
+## Concrete example
+
+A `RealizationalRule` meant to spell out a single agreement feature (`agr=match`) as a floating tone
+or zero-marking, with a `RequiredSyntacticFeatureStruct` that doesn't itself exclude re-application
+(e.g. it only requires `posV`, not "agr is not yet match"). A stratum's own rule cascade
+(`LinearRuleCascade`/`CombinationRuleCascade`, see `speed/stratum-rule-ordering.md`) tracks rules
+"not yet used on the current derivation path" within a single pass through that cascade, so within
+one straightforward pass the rule fires at most once. But whenever a derivation reaches the same
+rule's application point a second time by a different route — e.g. compounding
+(`SynthesisCompoundingRule`) combines two independently-derived sub-words, each of which already ran
+the realizational rule once on its own branch, or a longer, multi-stratum derivation feeds a word
+back through the same stratum again later — nothing revokes or dedupes a prior application, and
+`IsBlocked`'s feature-subset test is the only thing standing between that and the realizational
+spellout being expressed twice in the combined result. A grammar author reasoning from
+`AffixProcessRule`'s default `MaxApplicationCount = 1` would not expect this rule type to need any
+extra guard against it.
+
+## Fix
+
+For a `RealizationalAffixProcessRule`, make sure `RequiredSyntacticFeatureStruct` (or the
+accumulated `RealizationalFeatureStruct` state it depends on) is specific enough that a second
+application is infeasible on its own terms — e.g. require the *absence* of the feature the rule sets,
+not merely the presence of the features that license it. Don't rely on an implicit per-rule
+application cap for this rule type; unlike `AffixProcessRule`/`CompoundingRule`, none exists.
diff --git a/Docs/ai-parser-help/broken/single-template-independent-slots-illegal-combos.md b/Docs/ai-parser-help/broken/single-template-independent-slots-illegal-combos.md
new file mode 100644
index 0000000000..b334fd9fe9
--- /dev/null
+++ b/Docs/ai-parser-help/broken/single-template-independent-slots-illegal-combos.md
@@ -0,0 +1,74 @@
+---
+title: "Independent optional slots in one template can license a combination two templates would have prevented"
+implements: src/SIL.Machine.Morphology.HermitCrab/SynthesisAffixTemplateRule.cs, src/SIL.Machine.Morphology.HermitCrab/SynthesisAffixTemplatesRule.cs, src/SIL.Machine.Morphology.HermitCrab/AffixTemplate.cs
+category: morphotactics
+symptom: wrong-parse
+grammar_visible: partially
+---
+
+## What it is
+
+H. Andrew Black's FLEx/HC conceptual intro (§2.1.2.2 and its follow-up discussion of using separate
+templates to force affix co-occurrence) describes a modeling choice: two affixes that must always
+co-occur (or never co-occur) can be forced into that relationship by putting them in two mutually
+exclusive templates (gated by disjoint required feature structures), where putting them in
+independent optional slots of *one* template cannot express that coupling — each slot's
+apply-or-skip choice is made independently of every other slot in the same template.
+
+## The mechanism
+
+Within one template, `SynthesisAffixTemplateRule.ApplySlots` recurses slot by slot, and for every
+*optional* slot it explores both branches — apply the slot's rule(s), and also fall through to the
+next slot without applying it:
+
+```csharp
+private void ApplySlots(Word input, int index, HashSet output)
+{
+ for (int i = index; i < _rules.Count; i++)
+ {
+ foreach (Word outWord in _rules[i].Apply(input))
+ ApplySlots(outWord, i + 1, output);
+ if (!_template.Slots[i].Optional)
+ return;
+ }
+ output.Add(input);
+}
+```
+
+(`SynthesisAffixTemplateRule.cs:37-55`). Nothing here couples slot `i`'s apply/skip choice to slot
+`j`'s — each optional slot's two branches are explored independently, so with `n` independent
+optional slots in one template, all `2^n` combinations of "applied at this slot or not" are
+reachable (modulo each rule's own required-feature/environment gates), including combinations the
+grammar author never intended to co-occur.
+
+Templates themselves, by contrast, *are* mutually exclusive per derivation in the sense that a
+template is chosen by its own gate: `SynthesisAffixTemplatesRule.Apply` only enters a template when
+`input.SyntacticFeatureStruct.IsUnifiable(_templates[i].RequiredSyntacticFeatureStruct)`
+(`SynthesisAffixTemplatesRule.cs:37`) — so a grammar author who wants "affix A and affix B only ever
+co-occur, never apply independently" can express that by putting A in one template (with a
+distinguishing required feature) and B in a separate template gated on the same required feature,
+rather than as two optional slots of one shared template. Note both templates that satisfy their gate
+still both get tried in the same call (the loop at `SynthesisAffixTemplatesRule.cs:33-54` does not
+`break` after the first applicable template) — the mutual exclusion Black describes comes from making
+the templates' `RequiredSyntacticFeatureStruct`s disjoint, not from the engine picking only one
+template automatically.
+
+## Concrete example
+
+A stratum's single template has two independent optional slots: slot 1 for an applicative marker
+`-ap`, slot 2 for an object-agreement suffix that is only supposed to be licensed *together with*
+`-ap` (the language only marks object agreement on applicativized verbs). Modeled as two independent
+optional slots in one template, the engine happily explores: neither fires, only `-ap` fires, only
+the agreement suffix fires, or both fire — all four are separately reachable derivations unless each
+rule's own `RequiredSyntacticFeatureStruct`/MPR-feature gates independently rule out the "just
+agreement, no applicative" case. If the grammar author didn't add such a gate (having assumed the
+template structure itself expressed the dependency), the illegal "bare object agreement with no
+applicative" form parses and generates successfully.
+
+## Fix
+
+Do not rely on template/slot placement alone to express a required co-occurrence between two
+affixes. Either add explicit `RequiredMprFeatures`/`RequiredSyntacticFeatureStruct` gates so the
+dependent affix's rule cannot apply without the feature the other affix's rule sets, or split the
+co-occurring pair into their own template with a disjoint required feature structure from the
+template(s) where they don't apply, following Black's separate-templates pattern.
diff --git a/Docs/ai-parser-help/broken/stem-name-affix-requirement-trace-misreading.md b/Docs/ai-parser-help/broken/stem-name-affix-requirement-trace-misreading.md
new file mode 100644
index 0000000000..49b1dcbbc5
--- /dev/null
+++ b/Docs/ai-parser-help/broken/stem-name-affix-requirement-trace-misreading.md
@@ -0,0 +1,75 @@
+---
+title: "A rule's requiredStemName is checked in the forward (synthesis) pass only — including during parsing"
+implements: src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/AnalysisAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/Morpher.cs
+category: lexicon
+symptom: silent-misconfiguration
+grammar_visible: partially
+---
+
+## What it is
+
+`AffixProcessRule.RequiredStemName` (`MorphologicalRules/AffixProcessRule.cs:66`) lets a grammar
+author say "this affix rule may only apply to a root that already carries stem name X." This is a
+different mechanism from a stem-name-restricted *allomorph* (`RootAllomorph.StemName`, checked in
+`RootAllomorph.CheckAllomorphConstraints`, `RootAllomorph.cs:65-70` — see the companion gotcha in
+[`stem-name-explicit-feature-requirement.md`](stem-name-explicit-feature-requirement.md) for that
+mechanism). This one is a rule-level gate, and it is enforced in exactly one place in the whole
+engine.
+
+## The mechanism
+
+`SynthesisAffixProcessRule.Apply` checks it directly:
+
+```csharp
+if (_rule.RequiredStemName != null && _rule.RequiredStemName != input.RootAllomorph.StemName)
+```
+
+(`MorphologicalRules/SynthesisAffixProcessRule.cs:106`). This compares the rule's required stem
+name against `input.RootAllomorph.StemName` — a property that is only meaningful once a specific
+root allomorph has been chosen, which is a synthesis-direction concept: synthesis builds a word
+forward from a chosen lexical entry and allomorph.
+
+`AnalysisAffixProcessRule.Apply` (`MorphologicalRules/AnalysisAffixProcessRule.cs`) never
+references `RequiredStemName` at all. This isn't an oversight — it's structural: analysis unapplies
+an affix rule by stripping it off a surface form *before* lexical lookup identifies which root (and
+therefore which root allomorph) produced the stem. There is nothing to compare against yet at that
+point in the pipeline.
+
+That does not mean parsing ignores `RequiredStemName` end to end. `Morpher.ParseWord` (`Morpher.cs`)
+does not return raw backward-unapplication results — it feeds every analysis candidate through
+`Synthesize(word, analyses)` (`Morpher.cs:283-299`), which performs lexical lookup and then reapplies
+the same forward `SynthesisAffixProcessRule.Apply` used for generation, filtering the result through
+`IsWordValid`. So the check at `SynthesisAffixProcessRule.cs:106` *does* run during parsing — just in
+the resynthesis/confirmation half of the pipeline, not the backward-stripping half.
+
+## Why this is easy to miss
+
+A grammar author or anyone reading a parser trace one rule-application at a time sees the backward
+`AnalysisAffixProcessRule` step for a `requiredStemName`-bearing rule succeed unconditionally — it
+never looks at stem names at all. The actual rejection only shows up later, tagged
+`FailureReason.RequiredStemName`, in what a trace reader would think of as the generation/synthesis
+half of a "parse." Debugging "why didn't this word parse" by reading only the analysis-rule trace
+entries shows nothing wrong; the answer is in the resynthesis-confirmation entries instead.
+
+## Concrete example
+
+POS `posV`; feature `pers` with values `sym1`/`sym2`/`sym3`. Lexical entry `eRoot` has two
+allomorphs: `kap` (no stem name) and `tam` (stem name `snP1`, region `pers=sym1`). Suffix rule
+`rSuf1` (spells out `-xi`) declares `requiredStemName="snP1"`.
+
+Parsing the surface form `kap-xi`: the backward `AnalysisAffixProcessRule` pass for `rSuf1` strips
+`-xi` and reports success regardless of which root eventually gets matched. Lexical lookup then
+matches the residual shape `kap` to the `kap` allomorph (no stem name). Resynthesis reapplies
+`rSuf1` forward with `input.RootAllomorph` pointing at the `kap` allomorph, hits
+`RequiredStemName != input.RootAllomorph.StemName` (`snP1 != null`), and the candidate is dropped —
+never rejected in the initial unapplication step, only in the later confirmation step.
+
+## Fix
+
+When a parse unexpectedly fails (or unexpectedly succeeds) for a rule with `requiredStemName`, check
+the resynthesis/confirmation trace entries (`FailureReason.RequiredStemName`), not just the backward
+rule-application trace. If you want a stem-name requirement to behave symmetrically and visibly in
+both directions, prefer modeling it as a stem-name-restricted *allomorph* region instead (or in
+addition) — that mechanism is checked through the shared `Allomorph.IsWordValid` path and is at
+least consistently a "final validity" check on both sides, rather than a rule-only gate that is
+silently absent from one half of the pipeline's own rule trace.
diff --git a/Docs/ai-parser-help/broken/stem-name-explicit-feature-requirement.md b/Docs/ai-parser-help/broken/stem-name-explicit-feature-requirement.md
new file mode 100644
index 0000000000..7e69f25b69
--- /dev/null
+++ b/Docs/ai-parser-help/broken/stem-name-explicit-feature-requirement.md
@@ -0,0 +1,58 @@
+---
+title: "A stem-name-restricted allomorph needs the feature explicitly assigned, not just compatible"
+implements: src/SIL.Machine.Morphology.HermitCrab/StemName.cs, src/SIL.Machine.Morphology.HermitCrab/RootAllomorph.cs
+category: lexicon
+symptom: silent-misconfiguration
+grammar_visible: "no — produces silent, correct-per-the-code parse failures with no obvious cause in the XML"
+---
+
+## What it is
+
+Stem names restrict a root allomorph to only be valid when the word's accumulated syntactic
+feature structure falls inside one of the stem name's declared regions; any *other* allomorph of
+the same lexical entry that has no stem name (or a different one) is only valid **outside** that
+region. This is the mechanism behind "principal parts" — a root with an irregular form for one
+paradigm cell and a regular form everywhere else.
+
+## The mechanism
+
+A stem name's required-match check tests the word's *current* feature structure against the
+stem name's declared region. Because that check tests the feature structure as it actually
+stands at the point of checking — not "is this compatible with the region," but "is this
+explicitly inside the region" — a bare stem (before any rule has assigned the relevant feature)
+does not automatically satisfy a stem name's region, even if the region only mentions one
+feature. A form with that feature totally unassigned is outside the region, not inside it.
+
+## Toy example
+
+A root `tam`/`kap` where `kap` is stem-name-restricted to require `pers=1`:
+
+```xml
+
+
+
+
+
+...
+
+
+ tam
+ kap
+
+
+```
+
+The bare root `kap` alone (no person feature assigned at all) has **zero** valid parses — the
+stem-name-restricted allomorph requires `pers=1` to be explicitly present, and an unmarked form
+doesn't count, even trivially. Only a rule that actually assigns `pers=1` (e.g. a first-person
+agreement rule) makes `kap`-derived forms valid; the bare root, and any form built with a rule
+assigning `pers=2`, can only surface as `tam`.
+
+## Gotcha and fix
+
+A grammar author who expects a stem-name-restricted allomorph to be usable "whenever nothing
+else says otherwise" will see silent, correct-per-the-code parse failures instead — nothing in a
+trace beyond a generic environment/region mismatch points at "you forgot to assign the feature
+this region requires." The fix is to make sure every morphological rule that's supposed to
+license a restricted stem name actually assigns the specific feature value the region requires,
+not just a value that happens to be compatible with it.
diff --git a/Docs/ai-parser-help/broken/stemname-partsofspeech-required-not-silent.md b/Docs/ai-parser-help/broken/stemname-partsofspeech-required-not-silent.md
new file mode 100644
index 0000000000..643860800a
--- /dev/null
+++ b/Docs/ai-parser-help/broken/stemname-partsofspeech-required-not-silent.md
@@ -0,0 +1,82 @@
+---
+title: "A StemName's partsOfSpeech is required by the loader — omitting it crashes, it doesn't silently reject everything"
+implements: src/SIL.Machine.Morphology.HermitCrab/XmlLanguageLoader.cs, src/SIL.Machine.Morphology.HermitCrab/StemName.cs, src/SIL.Machine.Morphology.HermitCrab/HermitCrabInput.dtd
+category: loader
+symptom: crash
+grammar_visible: yes
+---
+
+## What it is
+
+A `` element's `partsOfSpeech` attribute is declared `#REQUIRED` in the HC-XML DTD:
+
+```
+
+
+```
+
+(`HermitCrabInput.dtd:94-99`). This is worth checking against source rather than assuming, because
+the failure mode for omitting it is a crash, not — as one might guess from the compiled `StemName`
+class alone — a silent "empty POS constraint that matches nothing." The compiled `StemName` class
+(`StemName.cs`) doesn't even have a `PartsOfSpeech` property; part of speech is folded into each
+region's feature structure at load time, and the loader never tolerates a missing value for it.
+
+## The mechanism
+
+`XmlLanguageLoader.LoadStemName` reads and immediately uses the attribute with no null-check:
+
+```csharp
+private void LoadStemName(XElement stemNameElem)
+{
+ var posIDs = (string)stemNameElem.Attribute("partsOfSpeech");
+ FeatureSymbol[] pos = posIDs.Split(' ').Select(id => _posFeature.PossibleSymbols[id]).ToArray();
+ ...
+ foreach (XElement regionElem in stemNameElem.Elements("Regions").Elements("Region"))
+ {
+ var fs = new FeatureStruct();
+ fs.AddValue(_posFeature, pos);
+ ...
+ }
+}
+```
+
+(`XmlLanguageLoader.cs:323-342`). `posIDs` is a plain nullable string cast from the XML attribute
+with no fallback. If the attribute is truly absent, `posIDs` is `null` and `posIDs.Split(' ')` throws
+a `NullReferenceException` immediately during loading — before any word is ever parsed or generated,
+and before the stem name's regions are even built.
+
+In practice this null case is also caught earlier by XML validation on most runtimes:
+`XmlLanguageLoader.Load` sets `ValidationType = Type.GetType("Mono.Runtime") == null ?
+ValidationType.DTD : ValidationType.None` (`XmlLanguageLoader.cs:212`). On a non-Mono .NET runtime,
+the reader validates the document against the DTD as it parses, and a `StemName` element missing its
+`#REQUIRED` `partsOfSpeech` attribute fails validation with an explicit error naming the missing
+attribute — before `LoadStemName` ever runs. Only on Mono (`ValidationType.None`, no DTD validation)
+would an omitted attribute reach `LoadStemName` at all, where it produces the `NullReferenceException`
+described above instead. Either way, the outcome is a loud failure at load time, not a silently
+unusable stem name.
+
+A related but distinct trap: if `partsOfSpeech` is present but contains an ID that isn't a POS the
+grammar declared, `_posFeature.PossibleSymbols[id]` throws a `KeyNotFoundException` (or equivalent) —
+also a crash, also at load time, also not silent.
+
+## Why this is worth documenting anyway
+
+Even though the failure is loud rather than silent, the *error message itself* doesn't say "this
+stem name is unusable" in language a grammar author would immediately connect to their FLEx-side
+edit — a DTD validation error or a bare `NullReferenceException`/`KeyNotFoundException` stack trace
+from deep in `XmlLanguageLoader` gives no hint that the fix is "add a `partsOfSpeech` attribute to
+this `StemName` element" unless the reader already knows this loader code. The practical failure
+mode is "the grammar fails to load at all" (not "some words silently fail to parse"), which is an
+important distinction from the "rejects everything at runtime" framing this trap is sometimes
+described with — the actual failure happens at grammar-load time, for the whole grammar, not
+per-word at parse time.
+
+## Fix
+
+Always give every `` an explicit `partsOfSpeech` listing every POS the stem name should
+apply to. If a load fails with a `NullReferenceException` or DTD validation error mentioning
+`StemName`, check for a missing or misspelled `partsOfSpeech` attribute value first — the loader
+provides no default and no graceful degradation for it.
diff --git a/Docs/ai-parser-help/broken/unclassified-affix-bypasses-template-ordering.md b/Docs/ai-parser-help/broken/unclassified-affix-bypasses-template-ordering.md
new file mode 100644
index 0000000000..6e989198f6
--- /dev/null
+++ b/Docs/ai-parser-help/broken/unclassified-affix-bypasses-template-ordering.md
@@ -0,0 +1,82 @@
+---
+title: "An unclassified (or under-specified) affix rule bypasses normal template-ordering discipline"
+implements: src/SIL.Machine.Morphology.HermitCrab/Morpheme.cs, src/SIL.Machine.Morphology.HermitCrab/MorphologicalRules/SynthesisAffixProcessRule.cs, src/SIL.Machine.Morphology.HermitCrab/SynthesisAffixTemplatesRule.cs, FieldWorks Src/LexText/ParserCore/HCLoader.cs
+category: morphotactics
+symptom: wrong-parse
+grammar_visible: partially
+---
+
+## What it is
+
+FieldWorks distinguishes an affix's morphosyntactic status as inflectional, derivational, or
+unclassified (H. Andrew Black's FLEx/HC conceptual intro, §2.1.1 "Unclassified affixes," discusses
+how an unclassified affix is "relatively unconstrained as to where it can appear" and can cause
+spurious parses). This is not just a FLEx-side labeling convention: it compiles down to a real HC
+engine property, `Morpheme.IsPartial` (`Morpheme.cs:45`), and the engine gives an `IsPartial` rule
+genuinely different template-ordering permissions than an ordinary classified rule.
+
+## The mechanism
+
+`SynthesisAffixProcessRule.Apply` gates whether a non-template rule may apply after a template has
+already fired, and the gate is different depending on `IsPartial`:
+
+```csharp
+// if a final template was last applied,
+// do not allow a non-partial rule to apply unless the input is partial
+if (!_rule.IsTemplateRule && (input.IsLastAppliedRuleFinal ?? false)
+ && !input.IsPartial && !_rule.IsPartial)
+ return Enumerable.Empty(); // FailureReason.NonPartialRuleProhibitedAfterFinalTemplate
+
+// if a non-final template was last applied,
+// only allow a non-partial rule to apply unless the input is partial
+if (!_rule.IsTemplateRule && input.IsLastAppliedRuleFinal.HasValue
+ && !input.IsLastAppliedRuleFinal.Value && !input.IsPartial && _rule.IsPartial)
+ return Enumerable.Empty(); // FailureReason.NonPartialRuleRequiredAfterNonFinalTemplate
+```
+
+(`MorphologicalRules/SynthesisAffixProcessRule.cs:61-104`). In plain terms: an ordinary
+(non-partial) rule cannot apply after a *final* template unless the word itself is already partial,
+and a *partial* rule specifically can. `SynthesisAffixTemplatesRule.Apply` also skips template
+matching for a partial root entirely: `&& !input.RootAllomorph.Morpheme.IsPartial`
+(`SynthesisAffixTemplatesRule.cs:38`) — a partial morpheme's derivation doesn't go through the
+normal template-selection gate other roots go through.
+
+The FieldWorks loader sets `IsPartial` from the FLEx affix classification: an inflectional affix's
+`IsPartial` is `msa.SlotsRC.Count == 0` (no slot assigned — effectively still under-specified even
+though nominally "inflectional"), and an unclassified affix is *always* loaded with `IsPartial =
+true` regardless of anything else about it (both in `HCLoader.cs`, in the inflectional- and
+unclassified-affix loading methods respectively).
+
+## Why this matters
+
+A classified, fully slotted inflectional or derivational affix is confined to the normal
+template-ordering discipline: once a final template applies, no further non-partial affixation is
+allowed except from other partial rules. An unclassified affix sidesteps that discipline on both
+ends — it's allowed to apply where a normal rule would be refused (after a final template, if the
+word itself isn't already partial... note it still needs the word to satisfy the *other* branch, but
+the two branches together give a partial rule strictly more freedom around template boundaries than
+a non-partial one gets). A grammar author who leaves an affix unclassified because its
+morphosyntactic status genuinely doesn't matter to them can end up with an affix that combines with
+stems in positions relative to a stratum's affix templates that a fully classified affix of the same
+shape never could — producing analyses (or generable words) the author did not intend to license.
+
+## Concrete example
+
+A grammar has one inflectional affix template per POS, marked `final="true"` (nothing may apply
+after it). A derivational suffix `-caus` is left unclassified in FLEx instead of tagged derivational
+(perhaps because the modeler wasn't sure or didn't think it mattered). Because `IsPartial` is forced
+`true` for any unclassified affix, `-caus` is now specifically *permitted* to apply after the final
+inflectional template — stacking on top of a fully-inflected word — where a properly classified
+derivational affix would have been rejected by `NonPartialRuleProhibitedAfterFinalTemplate`. The
+result: words like a fully-inflected verb with `-caus` appended afterward parse (or generate)
+successfully, when the grammar's actual structure (derivation happens before inflection, never
+after) never intended to allow that ordering.
+
+## Fix
+
+Classify every affix as inflectional or derivational explicitly, and for inflectional affixes assign
+a real template slot (`msa.SlotsRC`) rather than leaving it empty — both avoid `IsPartial` being set
+in a way you didn't intend. Reserve "unclassified" for affixes that genuinely should be exempt from
+template-ordering constraints (rare), and check any unclassified affix's actual generable/parseable
+combinations against the templates you expect it to interact with, since it is not gated the same
+way the rest of the grammar's affixes are.
diff --git a/Docs/ai-parser-help/getting-started.md b/Docs/ai-parser-help/getting-started.md
new file mode 100644
index 0000000000..8a2eb5ed23
--- /dev/null
+++ b/Docs/ai-parser-help/getting-started.md
@@ -0,0 +1,121 @@
+# Get help from ChatGPT or Claude with your HermitCrab grammar
+
+Part of the [AI Parser Help reference](README.md). This page is for anyone with a FieldWorks
+Language Explorer (FLEx) project who wants an LLM's help with their HermitCrab parser/grammar —
+whether it's broken ("why won't this word parse," "why do I get a wrong analysis"), slow ("why
+is this so slow," "why do I get 500 analyses for one word"), or a modeling question ("how should
+I represent this in my grammar").
+
+Want help with real **interlinear texts** (`.flextext` files) instead of, or alongside, the
+grammar itself? See [`texts/getting-started.md`](texts/getting-started.md) — extracting and
+reasoning about connected corpus texts is a different workflow from the one below.
+
+## Step 1 — Extract your grammar as HermitCrab XML
+
+FieldWorks ships a tool, `GenerateHCConfig.exe`, that exports your project's grammar as the
+HermitCrab XML format (the same format HermitCrab itself parses from).
+
+**First, close the project in FLEx.** The tool loads the project file directly and fails
+with "currently open in another application" if FLEx (or anything else) still has it open.
+
+**Find the tool.** It's installed next to `FieldWorks.exe` itself, not on your PATH, and the
+exact folder depends on your FieldWorks version. Easiest way to find it — paste this into
+PowerShell:
+
+```powershell
+Get-ChildItem "C:\Program Files\SIL\FieldWorks*\GenerateHCConfig.exe", `
+ "C:\Program Files (x86)\SIL\FieldWorks*\GenerateHCConfig.exe" `
+ -ErrorAction SilentlyContinue
+```
+
+That prints the full path (e.g. `C:\Program Files\SIL\FieldWorks 9\GenerateHCConfig.exe`).
+If it prints nothing, your project's `RootCodeDir` registry override points somewhere else —
+search for `GenerateHCConfig.exe` under wherever FieldWorks itself is installed.
+
+**Run it** against your project's `.fwdata` file:
+
+```powershell
+& "C:\Program Files\SIL\FieldWorks 9\GenerateHCConfig.exe" "C:\path\to\YourProject.fwdata" "C:\path\to\YourProject-hc.xml"
+```
+
+(use the actual path `Get-ChildItem` printed above, and your own project's `.fwdata` path —
+typically under `Documents\My FieldWorks\\`)
+
+This produces the second file, `YourProject-hc.xml` — that's your grammar.
+
+### Or export from inside FLEx, which also gets your texts
+
+Newer FieldWorks builds can do this without the command line, and can include your interlinear
+texts alongside the grammar: **File > Export**, then choose *Export Grammar and Texts for AI
+Analysis*. You pick which texts to include (the picker lists a word count and an analysis count
+for each one) and a folder; FLEx writes `HCGrammar.xml`, one `.flextext` file per selected text,
+and an `export-instructions.md` into that folder.
+
+That last file is why this route is the easy one: it carries the reference links, the answering
+instructions, and the warning about trusting AI answers, so **you can drag and drop all the files
+in the folder into ChatGPT (or Claude) at once and go straight to Step 4 below**.
+Steps 2 and 3 below exist for the command-line route, which produces the grammar file alone.
+
+This export is off by default. To turn it on, set the environment variable `FLEX_AI_EXPORT` to `1`
+**before** starting FieldWorks — it only sees the environment it was launched with, so setting the
+variable while FLEx is running changes nothing. To set it for your Windows account permanently,
+paste this into PowerShell:
+
+```powershell
+[Environment]::SetEnvironmentVariable('FLEX_AI_EXPORT', '1', 'User')
+```
+
+Then start FieldWorks again. If the option still isn't in the Export list, your FieldWorks version
+predates it — use the `GenerateHCConfig.exe` steps above instead.
+
+## Step 2 — Copy the XML into ChatGPT or Claude
+
+Open the exported XML file, copy its contents, and paste them into your chat with ChatGPT
+or Claude as the first message (or attach the file directly if your chat supports file
+uploads — for a large grammar this is more reliable than pasting inline).
+
+## Step 3 — Point it to the HermitCrab reference
+
+Paste this URL into the same chat:
+
+```
+https://raw.githubusercontent.com/sillsdev/machine/master/docs/ai-parser-help/README.md
+```
+
+This tells the LLM where to find the (non-proprietary) documentation of how the HermitCrab
+engine itself works — rule ordering, affix templates, features, correctness pitfalls, complexity
+pitfalls, and grammar-authoring methodology — so it can reason about *your* grammar against the
+actual engine mechanics rather than guessing. If you already know your question is about
+wrongness, speed, or modeling approach specifically, you can paste the more specific
+`broken/README.md`, `speed/README.md`, or `workflow/README.md` URL instead.
+
+## Step 4 — Ask your question
+
+Some examples of what to ask, once both your grammar and the reference URL are in the chat:
+
+- "Why does the word `` fail to parse / parse with the wrong analysis?"
+- "Why is parsing this word so slow? Is there a combinatorial-explosion pattern in my
+ affix templates like the optional-slot one described in the reference?"
+- "I have a slot with N optional prefixes, most of them null — is there a better way to
+ model this in my grammar?"
+- "How should I model a class of nouns whose plural is irregular, given HermitCrab's
+ mechanisms?"
+- "Walk through how stratum `` would apply to the stem `