Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
31066bf
Verify MCP identity before doctor reports healthy
MongLong0214 Aug 13, 2026
651a40a
Make init's PATH stub a faithful MCP responder
MongLong0214 Aug 13, 2026
ecfc3b2
Rebuild on the canonical builder and refresh the artifact manifest
MongLong0214 Aug 14, 2026
d4b83d9
Bound synchronous MCP helper cleanup
MongLong0214 Aug 14, 2026
75ce3ef
Make unattended initiator evidence honest
MongLong0214 Aug 14, 2026
99872e7
Pin each probe outcome to the fixture that produces it
MongLong0214 Aug 14, 2026
b3c6d6c
Add Windows MCP probe regression checks
MongLong0214 Aug 14, 2026
654f11d
Run both Windows MCP probe regressions
MongLong0214 Aug 14, 2026
e976962
Run MCP probe through declared bundle
MongLong0214 Aug 14, 2026
b0a2076
Exercise Windows PATHEXT negative control
MongLong0214 Aug 14, 2026
f4b351a
Fix Windows MCP command resolution and cleanup
MongLong0214 Aug 14, 2026
3ac3415
Launch Windows command shims through cmd
MongLong0214 Aug 14, 2026
f1ccb6e
Exercise cmd launcher cleanup through its shim
MongLong0214 Aug 14, 2026
024e9a8
Keep Windows MCP launcher alive without a console
MongLong0214 Aug 14, 2026
2e62bd6
Exercise Windows child-tree cleanup negative control
MongLong0214 Aug 14, 2026
cd53af7
Restore Windows MCP child-tree cleanup
MongLong0214 Aug 14, 2026
660d469
Record the reviewed Windows probe workflow
MongLong0214 Aug 14, 2026
31d4c91
Stop pinning a probe code the scheduler chooses
MongLong0214 Aug 14, 2026
52292b3
Rebuild on the canonical builder after rebasing onto main
MongLong0214 Aug 14, 2026
1e61a2f
Stop the child-tree test's precondition depending on what it tests
MongLong0214 Aug 14, 2026
d769513
Rebuild on the canonical builder after rebasing onto F-001 and F-002
MongLong0214 Aug 14, 2026
bf9f0c9
Re-pin the CI workflow hash after changing the child-tree fixture
MongLong0214 Aug 14, 2026
6f70b85
Assert child-tree reclamation without requiring a healthy server first
MongLong0214 Aug 14, 2026
7a7ddc6
Make the child-tree step's failure carry its own diagnosis
MongLong0214 Aug 14, 2026
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
137 changes: 137 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,143 @@ jobs:
echo "--- the Windows hook path, reported rather than asserted (T-1124) ---"
"$shim" doctor || echo "(doctor reported problems -- T-1124 owns the Windows hook path)"

# `commitlore.cmd` is the installed program. The registration uses the
# bare command a host records, so this exercises Windows PATH + PATHEXT
# resolution inside the MCP probe without asserting the unrelated doctor
# findings described above.
- name: A bare installed MCP command is healthy through PATHEXT (#640)
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$shim = Join-Path $env:LOCALAPPDATA 'commitlore\bin\commitlore.cmd'
$bin = Split-Path -Parent $shim
$repo = Join-Path $env:RUNNER_TEMP 'commitlore-bare-mcp-probe'
Remove-Item -LiteralPath $repo -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $repo | Out-Null
git -C $repo init -q -b main
Set-Content -LiteralPath (Join-Path $repo '.commitlore-policy.json') -NoNewline '{ "mode": "auto", "unattended": true }'
Set-Content -LiteralPath (Join-Path $repo '.mcp.json') -NoNewline '{ "mcpServers": { "commitlore": { "command": "commitlore", "args": ["mcp"] } } }'
$env:Path = "$bin;$env:Path"
$stdout = Join-Path $repo 'doctor.json'
$stderr = Join-Path $repo 'doctor.err'
$process = Start-Process -FilePath $shim -ArgumentList @('doctor', '--json') -WorkingDirectory $repo -RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru
if (-not $process.WaitForExit(20000)) {
Stop-Process -Id $process.Id -Force
throw 'doctor did not return within 20 seconds'
}
Get-Content -LiteralPath $stderr | Write-Host
$report = Get-Content -LiteralPath $stdout -Raw | ConvertFrom-Json
$probe = $report.checks | Where-Object { $_.id -eq 'unattended-initiator' }
$probe | ConvertTo-Json -Depth 8 | Write-Host
if ($probe.status -ne 'ok' -or $probe.evidence.initiator -ne 'capture-tools-advertised') {
throw 'the bare installed command was not verified as a healthy capture initiator'
}

# A cmd launcher starts a separate Node MCP server, then waits. The PID
# file lets this step distinguish a probe that returned from one that
# actually reclaimed the launcher's descendant.
- name: An MCP probe reclaims a cmd launcher's child tree (#640)
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$shim = Join-Path $env:LOCALAPPDATA 'commitlore\bin\commitlore.cmd'
$repo = Join-Path $env:RUNNER_TEMP 'commitlore-mcp-child-tree'
Remove-Item -LiteralPath $repo -Recurse -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path $repo | Out-Null
git -C $repo init -q -b main
$pidFile = Join-Path $repo 'child.pid'
$server = Join-Path $repo 'child-server.cjs'
$wrapper = Join-Path $repo 'launcher.cmd'
@'
const { writeFileSync } = require('node:fs');
writeFileSync(process.env.MCP_PROBE_CHILD_PID, String(process.pid));
let buffer = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
buffer += chunk;
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
const message = JSON.parse(line);
if (message.id === 1) process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 1, result: { serverInfo: { name: 'commitlore', version: '1.0.0' } } }) + '\n');
if (message.id === 2) process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: 2, result: { tools: [{ name: 'commitlore_query' }, { name: 'commitlore_before_change' }, { name: 'commitlore_prepare_capture' }, { name: 'commitlore_verify_capture' }, { name: 'commitlore_stage_capture' }] } }) + '\n');
}
});
setInterval(() => {}, 1_000);
'@ | Set-Content -LiteralPath $server -NoNewline
# The wrapper runs node in the foreground and inherits the probe's
# pipes, so "the server answered" does not depend on the child-tree
# handling this step exists to test. `start /b` detached the child,
# which left the precondition resting on whether Windows happens to
# pass console handles through `start` — and a precondition that
# depends on the behaviour under test cannot tell a broken product
# from a broken fixture. cmd.exe is still the probe's direct child
# and node is still a grandchild, so the tree is the same one #640 is
# about; only the protocol path is now unambiguous.
@"
@echo off
"$env:ProgramFiles\nodejs\node.exe" "$server"
"@ | Set-Content -LiteralPath $wrapper -NoNewline
Set-Content -LiteralPath (Join-Path $repo '.commitlore-policy.json') -NoNewline '{ "mode": "auto", "unattended": true }'
@{ mcpServers = @{ commitlore = @{ command = $wrapper; args = @() } } } | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $repo '.mcp.json') -NoNewline
$env:MCP_PROBE_CHILD_PID = $pidFile
$stdout = Join-Path $repo 'doctor.json'
$stderr = Join-Path $repo 'doctor.err'
$process = Start-Process -FilePath $shim -ArgumentList @('doctor', '--json') -WorkingDirectory $repo -RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru
if (-not $process.WaitForExit(20000)) {
Stop-Process -Id $process.Id -Force
throw 'doctor did not return within 20 seconds'
}
$report = Get-Content -LiteralPath $stdout -Raw | ConvertFrom-Json
$probe = $report.checks | Where-Object { $_.id -eq 'unattended-initiator' }
$probe | ConvertTo-Json -Depth 8 | Write-Host

# Diagnostics, printed on every run so a failure carries its own cause.
# A step that only reports "timed out" teaches nothing the second time.
Write-Host '--- doctor stderr ---'
if (Test-Path -LiteralPath $stderr) { Get-Content -LiteralPath $stderr -Raw | Write-Host } else { Write-Host '(no stderr file)' }
Write-Host "--- pid file present: $(Test-Path -LiteralPath $pidFile) ---"
if (Test-Path -LiteralPath $pidFile) { Write-Host "pid: $((Get-Content -LiteralPath $pidFile -Raw).Trim())" }

# Does the protocol survive the cmd.exe hop at all? Drive the wrapper
# by hand. An answer here means stdio traverses cmd and the silence
# belongs to the probe; silence means the handles are the cause.
# mcp-probe.ts asks only for serverInfo.name and a non-empty version,
# which this mock satisfies, so a rejected shape would surface as
# foreign-server rather than as a timeout.
$selfIn = Join-Path $repo 'selftest.in'
$selfOut = Join-Path $repo 'selftest.out'
$selfErr = Join-Path $repo 'selftest.err'
Set-Content -LiteralPath $selfIn -NoNewline ('{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' + "`n")
$env:MCP_PROBE_CHILD_PID = Join-Path $repo 'selftest.pid'
$self = Start-Process -FilePath $wrapper -RedirectStandardInput $selfIn -RedirectStandardOutput $selfOut -RedirectStandardError $selfErr -PassThru -WindowStyle Hidden
if (-not $self.WaitForExit(5000)) { Stop-Process -Id $self.Id -Force -ErrorAction SilentlyContinue }
$answer = if (Test-Path -LiteralPath $selfOut) { (Get-Content -LiteralPath $selfOut -Raw) } else { '' }
if ([string]::IsNullOrWhiteSpace($answer)) {
Write-Host '--- wrapper driven by hand: silent => cmd.exe stdio is the cause ---'
} else {
Write-Host "--- wrapper driven by hand: answered => stdio traverses cmd; the silence is the probe's ---"
Write-Host $answer.Trim()
}
if (Test-Path -LiteralPath $selfErr) { Write-Host "selftest stderr: $((Get-Content -LiteralPath $selfErr -Raw).Trim())" }
$env:MCP_PROBE_CHILD_PID = $pidFile
# Reclamation is not conditional on the probe's verdict: `finish()` in
# mcp-probe.ts runs stopProbeChild on every exit, and the timeout path
# is the one that matters most — an unhealthy server is exactly when a
# child tree gets stranded. Gating on `status -eq 'ok'` made the
# precondition depend on the protocol surviving a cmd.exe hop, where
# cmd and the child both hold the same stdin and whichever reads first
# wins; that raced, and a raced precondition cannot tell a broken
# product from a broken fixture. The pid file is the honest
# precondition: it proves the child really ran, and says nothing about
# what this step asserts.
if (-not (Test-Path -LiteralPath $pidFile)) { throw 'the wrapper never started its child server' }
$childPid = [int](Get-Content -LiteralPath $pidFile -Raw)
Start-Sleep -Milliseconds 250
if (Get-Process -Id $childPid -ErrorAction SilentlyContinue) {
throw "MCP child process $childPid survived probe cleanup"
}

# T-1124 (#283): #71's install-root containment, executed on Windows against
# a wrapper install. A property verified on one platform is not a property of
# the product, and macOS is the only place it has ever been executed.
Expand Down
36 changes: 36 additions & 0 deletions dist/cli.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading