Export grammar and texts for AI analysis - #1070
Open
johnml1135 wants to merge 2 commits into
Open
Conversation
Adds "Export Grammar and Texts for AI Analysis" to the Export dialog: it writes the project's HC grammar (HCGrammar.xml) and one .flextext file per selected text into a chosen folder, via a WinForms text picker showing Words/Analyses counts per text and remembering the last selection. The text half runs through a new Publisher/Subscriber event (ExportTextsAsFlexText) answered by a listener registered in ITextDll, since InterlinVc/InterlinearExporter are only reachable there without a build-breaking reference cycle back to xWorks. The grammar half reuses the existing HCLoader/XmlLanguageWriter pipeline via a new xWorks -> ParserCore project reference. Bundles docs/ai-parser-help (44 files) from the sillsdev/machine repo's docs/hc-llm-guide branch into Docs/ai-parser-help, since a raw grammar/text export is not very interpretable by an LLM without it. The export's description links to it for both an LLM (raw URL) and a human reader (github.com/blob URL). Follows the fieldworks-code-commenting standard (no named collaborators, "see X" pointers, or provenance framing in doc comments) and pulls the picker dialog's OK/Cancel button text from xWorksStrings instead of hardcoding it, matching sibling dialogs. Also ignores .review/, pr-preflight's scratch directory, which had no .gitignore entry and kept showing up as an untracked stray. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148xarEFPC5GA38C1Zu17V4
The task-by-task implementation plan and the brainstorming-stage design spec have no lasting value once the code exists; their durable reasoning (architecture decisions, the parser-concurrency rationale, implementation gotchas) now lives in the PR description instead, since the fieldworks-code-commenting standard already bans .md file/section pointers from code comments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148xarEFPC5GA38C1Zu17V4
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1070 +/- ##
==========================================
+ Coverage 38.04% 38.07% +0.03%
==========================================
Files 1499 1505 +6
Lines 350127 350407 +280
Branches 40239 40267 +28
==========================================
+ Hits 133215 133427 +212
- Misses 187625 187677 +52
- Partials 29287 29303 +16
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds "Export Grammar and Texts for AI Analysis" to the Export dialog: pick some texts, pick a folder, and FLEx writes the project's HermitCrab grammar (
HCGrammar.xml) plus one.flextextfile per text there, ready to hand an LLM for linguistic analysis.The diff is bigger than the feature sounds because of one real constraint:
InterlinVc/InterlinearExporter(needed to write.flextext) live inITextDll, which already depends onxWorks(where the Export dialog lives) -- a reference the other way is a build-breaking cycle. So the text half goes through a newPublisher/Subscriberevent answered by a listener registered inMain.xml, reusing the exact idiomAreaListeneralready uses forGetContentControlParametersrather than inventing something new. The grammar half is simpler: a newxWorks -> ParserCorereference (verified acyclic) reuses the sameHCLoader/XmlLanguageWriterpipeline the existingGenerateHCConfigconsole tool already uses.Where to look:
xWorks -> ParserCore: no cycle back (ParserCoredoesn't referencexWorks; onlyParserUI, one layer up, references both).FlexTextAIExportListener(inITextDll) answeringExportTextsAsFlexText, registered inMain.xml.HCLoaderexception aborts the whole export, not just the grammar half -- deliberate, and now has a direct test.MessageBoxis shown by the UI thread afterRunTaskreturns, not from the background task itself -- a real cross-thread bug avoided during implementation.Deliberately not here: no guard against the live in-app parser running concurrently with this export (see the accordion below -- every sibling export has the same exposure today); no per-text format customization; no zip/incremental re-export.
Build/tests: full
./test.ps1managed suite, clean except 12 pre-existingRenderComparisonTestspixel-baseline diffs and one nativeTestViewstiming issue, both unrelated to any file this branch touches.gitlintclean. Manually verified end-to-end against theSena 3sample project.Reading this a year from now -- start here
This branch had a design spec and a step-by-step implementation plan
(
Docs/superpowers/specs/2026-08-15-grammar-text-export-design.mdandDocs/superpowers/plans/2026-08-15-grammar-text-export.md) while the workwas in progress. Both were deleted before merge -- the plan was a
task-by-task TDD checklist with no lasting value once the code exists, and
the spec's conclusions are either already expressed in code comments (where
the
fieldworks-code-commentingstandard permits it) or preserved here,since that standard specifically bans
.mdfile/section pointers from codecomments. This record is deliberately the only place some of this reasoning
survives -- if you're trying to understand why something here looks the
way it does, this is where to look, not the deleted files.
The layer cake
The grammar half never leaves
xWorks; the text half necessarily crosses aDLL boundary that a direct reference can't cross, which is the reason the
publish/subscribe hop exists at all.
Decisions, and why
ITextDllalready has a
ProjectReferencetoxWorks(it subclassesExportDialogfor
InterlinearExportDialog). Adding the reverse reference to reachInterlinVc/InterlinearExporterfromxWorkswould be a genuinebuild-breaking cycle, not a style preference. The fix reuses the exact
idiom
ExportDialog.EnsureViewInfo()already uses forEventConstants.GetContentControlParameters(answered byAreaListener,registered globally in
Main.xml) instead of inventing a new mechanism.HCLoader/XmlLanguageWriterexception aborts the wholeexport; a per-text FLExText failure only skips that one text.
HCLoaderalready catches per-item linguistic problems internally (badphonemes, bad affix processes, etc.) and routes them to its
IHCLoadErrorLoggerargument, so anything that escapes indicates a realbug, not messy grammar data -- worth failing loudly for, unlike a single
text's export failing independently of the others.
Texts/subfolder.HCGrammar.xmland every.flextextfile land directly in the chosen folder. Extensions alreadydisambiguate them, and a flat folder was the simpler, explicitly requested
layout.
than one. Words counts every word-token occurrence regardless of whether
it's been analyzed (a raw "how much text is this" signal); Analyses counts
only the subset with a real
IWfiAnalysis/IWfiGlossattached, whether bya human or an unreviewed parser guess (a "how much have I actually analyzed"
signal). Conflating them would have hidden exactly the distinction someone
picking texts for analysis actually cares about.
export. Traced the actual mechanism:
ParserConnection/ParserSchedulerruns in-process against the same
LcmCache, processing its queue via anIdleQueuetied to the UI thread'sApplication.Idleevent -- and a modalShowDialog()(which is what blocks the UI during this export) stillraises
Application.Idlein WinForms, so the parser can keep mutatingwordform analyses while this export's background thread reads the same
cache. This exposure already exists, unmitigated, in every sibling export
(LIFT, Phonology, Grammar Sketch, the existing per-text FLExText export) --
adding a bespoke guard to only this one would be inconsistent with that
precedent and out of proportion to a risk the codebase has apparently
tolerated for a long time. A deliberate choice, not an oversight.
HCGrammar.xml, notGrammar.xml. "Grammar" is already overloadedthree ways in this codebase: the Grammar Area (one of five top-level UI
areas), the existing "Grammar Sketch" export (an unrelated human-readable
linguistic-description document), and this HermitCrab-format grammar. The
filename is self-disambiguating even sitting alone in a folder of
.flextextfiles;CONTEXT.mdnow canonicalizes "HC grammar" as the termfor the third one.
Paths not taken
.resxfor the picker dialog, matching some sibling dialogs'pattern of
resources.ApplyResources(control, "control"). Used the sharedxWorksStrings.resxinstead (addingksAIExportColumn*,ksOK,ksCancel, etc.), matchingInterlinearExportDialog's simpler existingprecedent of pulling column text from a shared strings class directly in
code. Caught during review that this file's OK/Cancel buttons had
initially been hardcoded rather than pulled from either -- fixed to use
the shared resx.
InterlinVc/InterlinearExportervia thesame
DynamicLoader.CreateObject(assemblyPath, className)patternExportDialog.EnsureViewInfo()already uses to reflectively load anInterlinear-area control for the existing "Grammar Sketch" export. Rejected
because every actual method call and property access on those types would
then need reflection too (not just construction), which is far messier
than the pub/sub hop for a type this deeply used (
InterlinVc.LineChoices,InterlinearExporter.Create/WriteBeginDocument/ExportDisplay/WriteEndDocument).xWorksfromIStText.ParagraphsOS/Segment.Analyses, avoiding the cross-DLL problementirely. Rejected as substantial, error-prone duplication of exactly the
logic
InterlinearExporteralready gets right (headwords, morphemebreakdowns, gloss lines, multiple writing systems).
Surprising findings
HCLoader.Loadhas real, undocumented preconditions beyond "the cacheexists":
MorphologicalDataOA.ParserParametersmust already be a valid XMLfragment (
XElement.ParsethrowsArgumentNullExceptionon a blankproject's default
null), at least one phoneme set must exist(
PhonologicalDataOA.PhonemeSetsOS[0]is indexed directly), and thatphoneme set needs morph (
+) and word (#) boundary markers(
LoadCharacterDefinitionTablethrowsKeyNotFoundExceptionlooking oneup by representation otherwise). None of this is asserted anywhere in
HCLoaderitself; it was discovered by writing a test against a bareLcmCache.CreateCacheWithNewBlankLangProjand fixing each crash in turn.SIL.LCModel.ITextand theSIL.FieldWorks.ITextnamespace (ITextDll's own root namespace).Any test project that references both assemblies hits C#'s
enclosing-namespace lookup finding the sibling namespace before the
using SIL.LCModel;import, so bareITextisCS0118("is a namespace")wherever the containing code's own namespace nests under
SIL.FieldWorks.Fixed by qualifying as
SIL.LCModel.ITextat each use site.InterlinearTestBase's fixture already runs each test inside an ambientundo task, unlike a bare
LcmCachebuilt directly in a test -- wrappingobject creation in another
UndoableUnitOfWorkHelper.Doinside a test thatinherits from it throws
InvalidOperationException: Nested tasks are not supported, matching the pattern already used byComplexConcPatternModelTests(which creates objects with noUnitOfWorkwrapper at all).
Evidence
gitlint --ignore body-is-missing --commits origin/main..HEAD-- clean(exit 0) against the single squashed commit.
./test.ps1(full managed suite,-SkipNative) -- run repeatedly acrossthe branch's implementation and again after the rebase onto the current
origin/maintip and the two review-driven fixes. The only failures inany run are the same 12
RenderComparisonTestspixel-baseline diffs(
complex,custom-heavy,footnote-heavy,many-paragraphs,medium,multi-book) and one nativeTestViewstiming/rendering issue everytime, none of which touch
xWorks,ITextDll,FwUtils, orParserCore-- not re-run against a clean
origin/maincheckout to get a formalbefore/after baseline, but consistent and content-unrelated across every
run this branch produced.
GrammarTextsAIExportHelpers(word/analysis counting, filename sanitization),
GrammarExportLoadLogger,GrammarAndTextsAIExportSelectionDlg(selection persistence),ExportDialog.ExportGrammarAndTextsForAI(grammar write, per-text-failurerecording, exception propagation),
FlexTextAIExportListener(actual.flextextfile production), andExportTextsAsFlexTextRequest.fieldworks-winappskill,Sena 3sample project, Legacy/WinForms UI mode): the new option appears in the
Lexicon area's Export dialog, the picker and folder browser both work, and
the export produces the expected files.
Docs/ai-parser-help/'s 92 relative links and 4 anchors were verified toresolve post-copy (a PowerShell link-target-existence pass), so it needed
no link surgery when moved from the
sillsdev/machinerepo.This change is