Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions .claude/skills/fieldworks-winapp/scripts/Preflight-WinFormsMcp.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
.DESCRIPTION
The winforms-mcp server (@fnrhombus/winforms-mcp) is launched by the MCP client itself
(Claude Code reads .mcp.json; VS Code reads .vscode/mcp.json). This script does NOT start
the server — it verifies the prerequisites that make `winforms_launch_app` succeed, and
the server -- it verifies the prerequisites that make `winforms_launch_app` succeed, and
prints the exact FieldWorks.exe path to launch. Run it before a parity/screenshot session.

Checks:
1. node + npx are on PATH (the server runs under npx).
2. The @fnrhombus/winforms-mcp package resolves on the registry (will be fetched on first use).
3. Output/<Configuration>/FieldWorks.exe exists (the app the MCP drives) — else points at build.ps1.
4. ICU_DATA is discoverable (FieldWorks needs it; mirrors test.ps1's resolution) — advisory.
3. Output/<Configuration>/FieldWorks.exe exists (the app the MCP drives), else build.ps1.
4. ICU_DATA is discoverable (FieldWorks needs it; mirrors test.ps1's resolution), advisory.
5. .mcp.json registers winforms-mcp for Claude Code (the fix for "no winforms_* tools").

.PARAMETER Configuration
Expand Down Expand Up @@ -49,24 +49,25 @@ Write-Host "winforms-mcp preflight (FieldWorks WinForms control)" -ForegroundCol
Write-Host "Repo: $repoRoot`n"

# 1. node + npx
$node = (Get-Command node -ErrorAction SilentlyContinue)?.Source
$npx = (Get-Command npx -ErrorAction SilentlyContinue)?.Source
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
$node = if ($nodeCmd) { $nodeCmd.Source } else { $null }
$npxCmd = Get-Command npx -ErrorAction SilentlyContinue
$npx = if ($npxCmd) { $npxCmd.Source } else { $null }
Write-Check ([bool]$node) "node on PATH" $node
Write-Check ([bool]$npx) "npx on PATH" $npx

# 2. package resolves (validate a real semver; a stray error string must not count as success).
# Routed through `cmd /c` because PowerShell mangles the leading '@' of a scoped package name — the
# same Windows quirk that makes the MCP server itself launch via `cmd /c npx` in .mcp.json.
# 2. package resolves (a real semver; a stray error string must not count as success).
# `cmd /c` because PowerShell mangles a scoped name's '@', as .mcp.json does for the server.
$pkgVersion = $null
try { $pkgVersion = (& cmd /c 'npm view @fnrhombus/winforms-mcp version 2>NUL' | Select-Object -First 1) } catch {}
$pkgOk = ($pkgVersion -match '^\d+\.\d+\.\d+')
Write-Check $pkgOk "@fnrhombus/winforms-mcp resolves" `
($(if ($pkgOk) { "version $pkgVersion (fetched on first MCP use)" } else { 'npm view failed — check network/registry' }))
($(if ($pkgOk) { "version $pkgVersion (fetched on first MCP use)" } else { 'npm view failed -- check network/registry' }))

# 3. FieldWorks.exe
$exe = Join-Path $repoRoot "Output/$Configuration/FieldWorks.exe"
Write-Check (Test-Path $exe) "FieldWorks.exe built ($Configuration)" `
($(if (Test-Path $exe) { "winforms_launch_app path: $exe" } else { "missing — run: .\build.ps1 -Configuration $Configuration" }))
($(if (Test-Path $exe) { "winforms_launch_app path: $exe" } else { "missing -- run: .\build.ps1 -Configuration $Configuration" }))

# 4. ICU_DATA (advisory; FieldWorks needs ICU at runtime)
$icuOk = $false
Expand All @@ -76,13 +77,13 @@ else {
Select-Object -First 1
if ($cand) { $icuOk = $true; Write-Host " (set ICU_DATA=$($cand.FullName) if FieldWorks fails to start)" -ForegroundColor DarkGray }
}
Write-Check $icuOk "ICU data discoverable" $(if ($icuOk) { 'advisory' } else { 'advisory — FieldWorks may need ICU_DATA set' })
Write-Check $icuOk "ICU data discoverable" $(if ($icuOk) { 'advisory' } else { 'advisory -- FieldWorks may need ICU_DATA set' })

# 5. .mcp.json registers winforms-mcp for Claude Code
$mcpJson = Join-Path $repoRoot '.mcp.json'
$registered = (Test-Path $mcpJson) -and ((Get-Content $mcpJson -Raw) -match 'winforms-mcp')
Write-Check $registered ".mcp.json registers winforms-mcp (Claude Code)" `
($(if ($registered) { 'reconnect Claude Code to load the winforms_* tools (approve the project server when prompted)' } else { 'missing — Claude Code will not expose winforms_* tools' }))
($(if ($registered) { 'reconnect Claude Code to load the winforms_* tools (approve the project server when prompted)' } else { 'missing -- Claude Code will not expose winforms_* tools' }))

if ($PrewarmPackage -and $npx) {
Write-Host "`nPre-warming the MCP package..." -ForegroundColor Cyan
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
.DESCRIPTION
A FieldWorks dev build resolves its code/config (DistFiles: parts, layouts, configuration) from
HKCU\SOFTWARE\SIL\FieldWorks\9 RootCodeDir/RootDataDir. When those point at a DIFFERENT worktree than
the exe being launched, the main window can fail to build (blank window, empty UIA tree) — so this
the exe launched, the main window can fail to build (blank window, empty UIA tree), so this
skill aligns them to the running worktree before launch.

Because that registry is shared across all worktrees, this script will NOT clobber it if the other
worktree is actively relying on it. "Actively using it" =
(a) a FieldWorks.exe process is currently running from the other worktree's tree, OR
(b) the FieldWorks registry key was last written within the last 24 hours (FieldWorks writes these
dirs on startup, so a recent write ≈ a recent launch from some worktree).
dirs on startup, so a recent write ~= a recent launch from some worktree).
If neither holds, the script realigns the registry to this worktree automatically. If either holds,
it prints `RESULT=ASK_USER` and changes nothing, so the caller can ask the user before realigning.

Expand Down Expand Up @@ -110,5 +110,5 @@ if ($recent) {
}

Set-DevDirs $thisDist
Write-Host "[OK] Other worktree not active and not used in 24h — realigned the dev registry to this worktree." -ForegroundColor Green
Write-Host "[OK] Other worktree not active and not used in 24h -- realigned the dev registry to this worktree." -ForegroundColor Green
Write-Host "RESULT=REALIGNED"
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<command id="CmdReparseAllWords" label="_Reparse all words" message="ReparseAllWords"/>
<command id="CmdTryAWord" label="_Try a Word..." icon="tryAWord" message="TryAWord"/>
<command id="CmdParseCurrentWord" label="Parse _Current Word" message="ParseCurrentWord"/>
<command id="CmdParseWordsInCurrentText" label="Parse Words in Te_xt" message="ParseWordsInCurrentText"/>
<command id="CmdParseUnapprovedWordsInCurrentText" label="Parse Unapproved Words in Text" message="ParseUnapprovedWordsInCurrentText"/>
<command id="CmdParseAllWordsInCurrentText" label="_All Words in Text" message="ParseAllWordsInCurrentText"/>
<command id="CmdParseWordsWithoutApprovedAnalysis" label="_Words Without an Approved Analysis" message="ParseWordsWithoutApprovedAnalysis"/>
<command id="CmdClearSelectedWordParserAnalyses" label="Clear Current Word's Parser _Analyses" message="ClearSelectedWordParserAnalyses"/>
<command id="CmdReInitializeParser" label="Re_load Grammar / Lexicon" message="ReInitParser" tooltip="Reloads data from the Grammar and Lexicon, so the parser uses your latest changes."/>
<command id="CmdCheckParserOnCurrentText" label="On Current Text" message="CheckParserOnCurrentText" tooltip="Checks parser on currrent text and reports results."/>
Expand Down Expand Up @@ -281,8 +281,10 @@
<item command="CmdStopParser"/>
<item label="-" translate="do not translate"/>
<item command="CmdTryAWord"/>
<item command="CmdParseWordsInCurrentText"/>
<item command="CmdParseUnapprovedWordsInCurrentText"/>
<menu id="ParseWordsInTextMenu" label="Parse Words in Te_xt">
<item command="CmdParseAllWordsInCurrentText"/>
<item command="CmdParseWordsWithoutApprovedAnalysis"/>
</menu>
<item command="CmdParseCurrentWord"/>
<item command="CmdClearSelectedWordParserAnalyses" defaultVisible="false"/>
<item label="-" translate="do not translate"/>
Expand Down
29 changes: 20 additions & 9 deletions Src/LexText/ParserUI/ParserListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ public bool OnParseCurrentWord(object argument)
return true; //we handled this.
}

public bool OnDisplayParseWordsInCurrentText(object commandObject, ref UIItemDisplayProperties display)
public bool OnDisplayParseWordsInTextMenu(object commandObject, ref UIItemDisplayProperties display)
{
CheckDisposed();

Expand All @@ -528,7 +528,7 @@ public bool OnDisplayParseWordsInCurrentText(object commandObject, ref UIItemDis
return true; //we handled this.
}

public bool OnDisplayParseUnapprovedWordsInCurrentText(object commandObject, ref UIItemDisplayProperties display)
public bool OnDisplayParseAllWordsInCurrentText(object commandObject, ref UIItemDisplayProperties display)
{
CheckDisposed();

Expand All @@ -539,7 +539,18 @@ public bool OnDisplayParseUnapprovedWordsInCurrentText(object commandObject, ref
return true; //we handled this.
}

public bool OnParseWordsInCurrentText(object argument)
public bool OnDisplayParseWordsWithoutApprovedAnalysis(object commandObject, ref UIItemDisplayProperties display)
{
CheckDisposed();

bool enable = CurrentText != null;
display.Visible = enable;
display.Enabled = enable;

return true; //we handled this.
}

public bool OnParseAllWordsInCurrentText(object argument)
{
CheckDisposed();

Expand All @@ -553,23 +564,23 @@ public bool OnParseWordsInCurrentText(object argument)
return true; //we handled this.
}

public bool OnParseUnapprovedWordsInCurrentText(object argument)
public bool OnParseWordsWithoutApprovedAnalysis(object argument)
{
CheckDisposed();

if (CurrentText != null && ConnectToParser())
{
IStText text = CurrentText;
IEnumerable<IWfiWordform> wordforms = GetUnapprovedWordforms(text);
IEnumerable<IWfiWordform> wordforms = GetWordformsWithoutApprovedAnalysis(text);
UpdateWordforms(wordforms, ParserPriority.Medium);
}

return true; //we handled this.
}

private IEnumerable<IWfiWordform> GetUnapprovedWordforms(IStText text)
private IEnumerable<IWfiWordform> GetWordformsWithoutApprovedAnalysis(IStText text)
{
HashSet<IWfiWordform> unapprovedWordforms = new HashSet<IWfiWordform>();
HashSet<IWfiWordform> wordformsWithoutApprovedAnalysis = new HashSet<IWfiWordform>();
foreach (IStTxtPara para in text.ParagraphsOS)
{
foreach (ISegment seg in para.SegmentsOS)
Expand All @@ -590,13 +601,13 @@ private IEnumerable<IWfiWordform> GetUnapprovedWordforms(IStText text)
}
if (!approved)
{
unapprovedWordforms.Add(wordform);
wordformsWithoutApprovedAnalysis.Add(wordform);
}
}
}
}
}
return unapprovedWordforms;
return wordformsWithoutApprovedAnalysis;
}

private bool HasApprovedAnalysis(IWfiWordform wordform)
Expand Down
Loading