Export grammar and texts for AI analysis - #1070
Open
johnml1135 wants to merge 4 commits into
Open
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1070 +/- ##
==========================================
+ Coverage 38.04% 38.08% +0.03%
==========================================
Files 1499 1506 +7
Lines 350127 350433 +306
Branches 40239 40272 +33
==========================================
+ Hits 133215 133460 +245
- Misses 187625 187672 +47
- Partials 29287 29301 +14
🚀 New features to boost your workflow:
|
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
The "Export Grammar and Texts for AI Analysis" option is now opt-in: AiExportGate reads FLEX_AI_EXPORT, and ExportDialog leaves the template out of the export list entirely when it is unset, so the option is absent rather than greyed out. Value handling matches the existing FW_AVALONIA gate: unset, blank, "0", "false", and "off" all mean off. The description shown for the option now opens with a warning that AI answers can be very wrong while sounding certain, and that a change must be understood and confirmed against the project's own data before being applied. The bundled AI Parser Help reference gains a "How to answer" section addressed to the LLM: plain language for a field linguist, accuracy before simplification, every technical term defined on first use and no bare abbreviations, FLEx's own labels rather than engine class names, and an explicit flag on every guess. Its getting-started page now also documents the in-app export route and the environment variable it needs, and warns readers not to trust an answer without checking it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
johnml1135
force-pushed
the
grammar-text-export
branch
from
August 17, 2026 12:04
850582a to
a064539
Compare
The AI-analysis export now copies a shipped instructions file into the chosen folder as export-instructions.md, so the whole folder can be dragged into a chat in one go and still carry everything the assistant needs: what each file is, the raw URL of the AI Parser Help reference to read first, the plain-language answering rules, and the warning about trusting AI answers. A missing shipped file is reported in the export summary rather than aborting, since the grammar and texts are still usable without it. The description in the Export dialog drops the two URLs it used to ask the user to paste by hand, and says to drag and drop all the files instead. The reference's getting-started page gains the same shortcut: the in-app export route can skip its paste-the-XML and paste-the-URL steps entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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), one.flextextfile per text, and anexport-instructions.mdthere. The user's whole job after that is to drag and drop the files into ChatGPT (or Claude) and ask their question -- the instructions file carries the reference links, the answering rules, and the warning, so there is nothing to paste by hand.The option is opt-in and invisible by default -- it appears only when
FLEX_AI_EXPORTis set in the environment FieldWorks was launched with, so nothing changes for any user who hasn't gone looking for it. When it does appear, its description opens with a warning that AI answers can be very wrong while sounding certain and must be understood and confirmed before being applied.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:
AiExportGate(inFwUtils, same value rules as the existingFW_AVALONIAgate) andExportDialog.AddFxts, which skips the template file outright so the row is absent from the list rather than greyed out.AIExportInstructions.mdunderDistFiles/Language Explorer/Export Templates/, copied into the export folder asexport-instructions.mdbyExportDialog.CopyAiExportInstructions.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.
DistFilesfile copied into the export, not asa string in code.
export-instructions.mdexists so the exported folder isself-contained: drop all of it into a chat and the assistant learns what each
file is, which reference URL to read first, how to answer in plain language,
and that its answers must be checked. Keeping the text in
DistFiles/Language Explorer/Export Templates/AIExportInstructions.mdputs itwhere the sibling exports already keep their non-code assets (the
.xslfilesnext to it), lets the wording be corrected without a rebuild, and keeps a
70-line markdown document out of a
.resx. The installer harvestsDistFileswholesale viaheat dir, so it ships with no installer change.A missing shipped file lands in the export summary instead of aborting -- the
grammar and texts are still usable without it.
into
export-instructions.md, where the assistant reads them itself. Asking auser to copy a raw
githubusercontent.comURL into a chat before asking aquestion was the most fragile step in the flow and the easiest one to skip.
FLEX_AI_EXPORT, and absent rather than disabled. Anexport that hands linguistic data to a third-party chat service shouldn't
be one misclick away for someone who never asked for it, so the default is
off.
ExportDialog.AddFxtsskips the template file entirely when thevariable is unset -- a greyed-out row would still advertise the feature and
invite "how do I enable this?" without answering it. The value rules
(unset/blank/
0/false/offmean off;1/true/yesmean on) copy theexisting
FW_AVALONIAgate inUIModeGatesrather than inventing newspelling.
AiExportGateis a separate class rather than a new method onUIModeGatesbecause the two flags gate unrelated things and shouldn't beable to drift into each other.
live in the reference the LLM reads. Two different audiences, so two
different places. The description (shown in the dialog before the user
commits to anything) warns the human that answers can be confidently wrong
and must be checked against their own data.
Docs/ai-parser-help/README.md-- the file the description tells the user to paste into the chat -- gains a
"How to answer" section addressed to the LLM: plain language for a field
linguist, accuracy before simplification, every technical term defined on
first use, no bare abbreviations, FLEx's own labels rather than engine class
names, and an explicit flag on every guess. Those rules are adapted from
this repo's own
fieldworks-code-commentingstandard, retargeted from "thenext reader of the code" to "the linguist who asked".
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 every commit on the branch.
ExportGrammarAndTextsForAI_CopiesTheShippedInstructionsIntoTheFolderasserts the shipped file actually lands in the export folder under the name
the dialog's description promises, rather than trusting the copy.
AiExportGateTests(which values countas opted in, plus one asserting the process-environment default is off) and
ExportDialogAiGateTests(which template shapes the gate applies to).Both pass, as do the pre-existing AI-export tests, with the full solution
building 0 warnings / 0 errors.
./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