diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10d9c2da..e23f0851 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -820,6 +820,159 @@ 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)); + // Wall clock at child start. Against the moment doctor was invoked + // this says whether the cold start fits inside the probe's five + // second window or lands after it; "slow" and "unreachable" produce + // the same silence otherwise. + writeFileSync(process.env.MCP_PROBE_CHILD_PID + '.startedat', String(Date.now())); + let buffer = ''; + const started = Date.now(); + process.stdin.setEncoding('utf8'); + // Whether the probe's bytes ever arrive is the question the log could + // not answer. Record the first chunk and when it came. + process.stdin.once('data', () => { + writeFileSync(process.env.MCP_PROBE_CHILD_PID + '.sawinput', String(Date.now() - started)); + }); + 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. + # setup-node puts the requested Node in hostedtoolcache and puts that + # on PATH; it does not update C:\Program Files\nodejs, which may hold + # a different runner-image Node or none at all. Hard-coding that path + # made the fixture depend on the image rather than on the job, and + # cmd.exe fails silently on a missing interpreter — which is why three + # independent attempts produced silence and no child pid. + $nodeExe = (Get-Command node).Source + Write-Host "--- launcher will run: $nodeExe ---" + Write-Host "--- C:\Program Files\nodejs\node.exe present: $(Test-Path -LiteralPath (Join-Path $env:ProgramFiles 'nodejs\node.exe')) ---" + @" + @echo off + "$nodeExe" "$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 + $doctorStartedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() + $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())" } + # The question the previous round could not answer: did the probe's + # bytes reach the child at all? mcp-probe.ts waits a fixed 200ms + # before its first protocol write, so "arrived late" and "never + # arrived" are different failures and only one of them is a race. + $startedAt = "$pidFile.startedat" + if (Test-Path -LiteralPath $startedAt) { + $delta = [int64]("$(Get-Content -LiteralPath $startedAt -Raw)".Trim()) - $doctorStartedAt + Write-Host "--- child process started ${delta}ms after doctor was invoked (probe window is 5000ms) ---" + } else { + Write-Host '--- child never recorded a start time ---' + } + $sawInput = "$pidFile.sawinput" + if (Test-Path -LiteralPath $sawInput) { + Write-Host "--- child saw stdin after $("$(Get-Content -LiteralPath $sawInput -Raw)".Trim())ms => the write arrived; the missing half is the response ---" + } else { + Write-Host '--- child never saw stdin => the probe write did not reach it ---' + } + + # 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. diff --git a/dist/cli.js b/dist/cli.js index c2dea629..afec6476 100644 --- a/dist/cli.js +++ b/dist/cli.js @@ -16,6 +16,7 @@ import { register as registerAuto } from './commands/auto.js'; import { register as registerCapture } from './commands/capture.js'; import { register as registerDemo } from './commands/demo.js'; import { packageVersion } from './core/paths.js'; +import { probeMcp } from './core/mcp-probe.js'; import { formatRuntimeIdentity, runtimeIdentity } from './core/runtime-identity.js'; import { register as registerDoctor } from './commands/doctor.js'; import { register as registerHarvest } from './commands/harvest.js'; @@ -42,6 +43,28 @@ import { register as registerPrePush } from './hooks/pre-push.js'; import { labelRecordBlocks, serializeTrailers } from './core/trailers.js'; const pkg = { version: packageVersion() }; const STDIN_FD = 0; +const internalMcpProbe = async (command, rawArgs) => { + let args; + try { + const parsed = JSON.parse(rawArgs ?? 'null'); + if (Array.isArray(parsed) && parsed.every((value) => typeof value === 'string')) + args = parsed; + } + catch { + // The result below is a probe response, not an uncaught helper error. + } + const result = command === undefined || args === undefined + ? { kind: 'failure', reason: 'probe-unavailable', detail: 'could not read MCP verification arguments' } + : await probeMcp(command, args); + await new Promise((resolve) => process.stdout.write(JSON.stringify(result), () => resolve())); +}; +const internalArguments = process.argv.slice(2); +if (internalArguments[0] === 'internal' && internalArguments[1] === 'mcp-probe') { + const commandIndex = internalArguments.indexOf('--command'); + const argsIndex = internalArguments.indexOf('--args-json'); + await internalMcpProbe(commandIndex === -1 ? undefined : internalArguments[commandIndex + 1], argsIndex === -1 ? undefined : internalArguments[argsIndex + 1]); + process.exit(0); +} const readMessage = (messageFile) => { if (messageFile !== undefined) return readFileSync(messageFile, 'utf8'); @@ -123,6 +146,19 @@ program .action((options) => { runParse(options); }); +/** + * An implementation-only bridge for synchronous doctor checks. Both command + * levels are hidden so the bundle keeps one public command surface while the + * probe can still run the same bundled code in a child process. + */ +program + .command('internal', { hidden: true }) + .command('mcp-probe', { hidden: true }) + .requiredOption('--command ', 'MCP command to verify') + .requiredOption('--args-json ', 'JSON array of MCP command arguments') + .action(async (options) => { + await internalMcpProbe(options.command, options.argsJson); +}); registerSync(program); registerPrePush(program); registerValidate(program); diff --git a/dist/cli.js.map b/dist/cli.js.map index 6fbb85ad..a5b049db 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/commands/doctor/checks/capture-unattended-initiator.js b/dist/commands/doctor/checks/capture-unattended-initiator.js index 935d5871..671c095a 100644 --- a/dist/commands/doctor/checks/capture-unattended-initiator.js +++ b/dist/commands/doctor/checks/capture-unattended-initiator.js @@ -6,7 +6,8 @@ * commit can begin that run. */ import { POLICY_FILE_NAME, resolvePolicy } from '../../../core/capture-policy.js'; -import { MCP_REGISTRATION_FILE, registeredMcpCommand, registersCommitloreMcpServer, registrationIsOurs, } from '../../../core/mcp-registration.js'; +import { MCP_REGISTRATION_FILE, registeredMcpCommand, registeredMcpLaunch, registersCommitloreMcpServer, registrationIsOurs, } from '../../../core/mcp-registration.js'; +import { isMcpProbeFailure, probeMcpSync } from '../../../core/mcp-probe.js'; import { check } from '../model.js'; /** * #527: a policy file said unattended capture was enabled, while normal Git @@ -56,33 +57,55 @@ export const checkUnattendedCaptureInitiator = (ctx) => { }, }); } - if (registersCommitloreMcpServer(cwd)) { - // Registered and *ours* is the only combination this can vouch for. - // Anything else is an entry an operator chose, which is theirs to keep and - // not this report's to call verified: `{"command": "false"}` was reading as - // a working capture server. + const launch = registeredMcpLaunch(cwd); + if (launch !== null && registersCommitloreMcpServer(cwd)) { const command = registeredMcpCommand(cwd); const ours = registrationIsOurs(cwd); - if (!ours) { - return check(id, category, title, 'warn', `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, which is not the command ` + - 'this tool writes — it is left alone, and whether it starts a capture server is unverified', `check that ${JSON.stringify(command)} is a CommitLore MCP server, or remove the entry and run commitlore init`, false, undefined, { + const probe = probeMcpSync(launch.command, launch.args); + if (isMcpProbeFailure(probe)) { + // A timeout is an observation about this attempt, not a verdict about + // the registration (#640). Measured on a Windows runner: a *passing* + // probe consumed 4478ms of its 5000ms budget, because the chain is + // doctor -> sidecar node -> cmd.exe -> the server's own node, three + // process starts before a byte is exchanged. On a slower machine the + // same healthy registration reports the same silence — and saying "it is + // unhealthy, repair it" sends an operator to fix something that works. + const unverified = probe.reason === 'initialize-timed-out'; + return check(id, category, title, 'warn', unverified + ? `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, and it did not answer in time to be verified: ${probe.detail}. This does not say the registration is broken — a cold start on a loaded machine can outlast the probe.` + : `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, but it is unhealthy: ${probe.detail}`, unverified + ? `rerun commitlore doctor when the machine is quieter; if it keeps timing out, start ${JSON.stringify(command)} by hand and check that it answers an MCP initialize` + : `repair ${JSON.stringify(command)} so it answers as a CommitLore MCP server, or remove the entry and run commitlore init`, false, undefined, { evidence: { policy: 'unattended', ordinary_git_commit: 'cannot-initiate', - initiator: 'registered-command-unverified', + initiator: unverified ? 'registered-command-unverified' : 'registered-command-unhealthy', command: command ?? '', + probe: probe.reason, }, }); } - return check(id, category, title, 'ok', `${MCP_REGISTRATION_FILE} registers the capture server, so a host loading it can start unattended capture; ` + - 'an ordinary git commit outside that host still cannot', null, false, undefined, { + if (probe.kind === 'read-delivery') { + return check(id, category, title, 'warn', `${MCP_REGISTRATION_FILE} registers a live CommitLore read-delivery server${ours ? '' : ' through a custom wrapper'}, but it does not advertise all three capture tools, so it is not an unattended capture initiator`, 'register a CommitLore MCP server that advertises commitlore_prepare_capture, commitlore_verify_capture, and commitlore_stage_capture', false, undefined, { + evidence: { + policy: 'unattended', + ordinary_git_commit: 'cannot-initiate', + initiator: 'read-delivery-only', + registration: ours ? 'installer-owned' : 'custom-preserved', + verified: 'identity-and-read-tools', + capture_tools: 'not-complete', + }, + }); + } + return check(id, category, title, 'ok', `${MCP_REGISTRATION_FILE} registers a live CommitLore MCP server${ours ? '' : ' through a custom wrapper'} that advertises the required read and capture tools; ` + + 'this verifies identity and tool set, not asset readiness — required assets such as SPEC.md can still be missing and make prepare fail; an ordinary git commit outside that host still cannot initiate capture', null, false, undefined, { evidence: { policy: 'unattended', ordinary_git_commit: 'cannot-initiate', - initiator: 'mcp-server-registered', - // Registration is configuration, not observation: nothing here - // proves a host has ever called the tool. - verified: 'registration-only', + initiator: 'capture-tools-advertised', + registration: ours ? 'installer-owned' : 'custom-preserved', + verified: 'identity-and-read-and-capture-tools', + asset_readiness: 'not-verified', }, }); } diff --git a/dist/commands/doctor/checks/capture-unattended-initiator.js.map b/dist/commands/doctor/checks/capture-unattended-initiator.js.map index 5495e6b2..c45fabc0 100644 --- a/dist/commands/doctor/checks/capture-unattended-initiator.js.map +++ b/dist/commands/doctor/checks/capture-unattended-initiator.js.map @@ -1 +1 @@ -{"version":3,"file":"capture-unattended-initiator.js","sourceRoot":"","sources":["../../../../src/commands/doctor/checks/capture-unattended-initiator.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"capture-unattended-initiator.js","sourceRoot":"","sources":["../../../../src/commands/doctor/checks/capture-unattended-initiator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAClF,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,4BAA4B,EAC5B,kBAAkB,GACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,KAAK,EAAuD,MAAM,aAAa,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,GAAkB,EAAe,EAAE;IACjF,MAAM,EAAE,GAAG,sBAAsB,CAAC;IAClC,MAAM,KAAK,GAAG,8BAA8B,CAAC;IAC7C,MAAM,QAAQ,GAAa,SAAS,CAAC;IACrC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1C,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAEtC,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QACnB,OAAO,KAAK,CACV,EAAE,EACF,QAAQ,EACR,KAAK,EACL,MAAM,EACN,GAAG,gBAAgB,6FAA6F,EAChH,wBAAwB,EACxB,KAAK,EACL,SAAS,EACT;YACE,QAAQ,EAAE;gBACR,MAAM,EAAE,UAAU;gBAClB,YAAY,EAAE,UAAU,CAAC,KAAK,IAAI,SAAS;gBAC3C,mBAAmB,EAAE,iBAAiB;aACvC;SACF,CACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,KAAK,CACV,EAAE,EACF,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,0DAA0D,EAC1D,IAAI,EACJ,KAAK,EACL,SAAS,EACT;YACE,QAAQ,EAAE;gBACR,MAAM,EAAE,KAAK;gBACb,mBAAmB,EAAE,iBAAiB;gBACtC,SAAS,EAAE,gBAAgB;aAC5B;SACF,CACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,MAAM,KAAK,IAAI,IAAI,4BAA4B,CAAC,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,sEAAsE;YACtE,qEAAqE;YACrE,mEAAmE;YACnE,oEAAoE;YACpE,qEAAqE;YACrE,yEAAyE;YACzE,uEAAuE;YACvE,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,KAAK,sBAAsB,CAAC;YAC3D,OAAO,KAAK,CACV,EAAE,EACF,QAAQ,EACR,KAAK,EACL,MAAM,EACN,UAAU;gBACR,CAAC,CAAC,GAAG,qBAAqB,cAAc,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,oEAAoE,KAAK,CAAC,MAAM,0GAA0G;gBACzP,CAAC,CAAC,GAAG,qBAAqB,cAAc,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,2CAA2C,KAAK,CAAC,MAAM,EAAE,EAC1H,UAAU;gBACR,CAAC,CAAC,sFAAsF,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,sDAAsD;gBACrK,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,wFAAwF,EAC7H,KAAK,EACL,SAAS,EACT;gBACE,QAAQ,EAAE;oBACR,MAAM,EAAE,YAAY;oBACpB,mBAAmB,EAAE,iBAAiB;oBACtC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,8BAA8B;oBACxF,OAAO,EAAE,OAAO,IAAI,EAAE;oBACtB,KAAK,EAAE,KAAK,CAAC,MAAM;iBACpB;aACF,CACF,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACnC,OAAO,KAAK,CACV,EAAE,EACF,QAAQ,EACR,KAAK,EACL,MAAM,EACN,GAAG,qBAAqB,oDAAoD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,2BAA2B,mGAAmG,EACtN,sIAAsI,EACtI,KAAK,EACL,SAAS,EACT;gBACE,QAAQ,EAAE;oBACR,MAAM,EAAE,YAAY;oBACpB,mBAAmB,EAAE,iBAAiB;oBACtC,SAAS,EAAE,oBAAoB;oBAC/B,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB;oBAC3D,QAAQ,EAAE,yBAAyB;oBACnC,aAAa,EAAE,cAAc;iBAC9B;aACF,CACF,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CACV,EAAE,EACF,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,GAAG,qBAAqB,0CAA0C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,2BAA2B,wDAAwD;YAC/J,+MAA+M,EACjN,IAAI,EACJ,KAAK,EACL,SAAS,EACT;YACE,QAAQ,EAAE;gBACR,MAAM,EAAE,YAAY;gBACpB,mBAAmB,EAAE,iBAAiB;gBACtC,SAAS,EAAE,0BAA0B;gBACrC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB;gBAC3D,QAAQ,EAAE,qCAAqC;gBAC/C,eAAe,EAAE,cAAc;aAChC;SACF,CACF,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CACV,EAAE,EACF,QAAQ,EACR,KAAK,EACL,MAAM,EACN,wJAAwJ,EACxJ,0GAA0G,EAC1G,KAAK,EACL,SAAS,EACT;QACE,QAAQ,EAAE;YACR,MAAM,EAAE,YAAY;YACpB,mBAAmB,EAAE,iBAAiB;YACtC,SAAS,EAAE,qBAAqB;SACjC;KACF,CACF,CAAC;AACJ,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/commands/installer-hosts.d.ts b/dist/commands/installer-hosts.d.ts index 7041474c..aba06869 100644 --- a/dist/commands/installer-hosts.d.ts +++ b/dist/commands/installer-hosts.d.ts @@ -30,8 +30,6 @@ interface Options { dataRoot: string; home: string; } -/** Speak enough MCP to distinguish an executable from a usable CommitLore server. */ -export declare const probeMcp: (command: string, args: string[]) => Promise; export declare const inspectAndApplyHosts: (options: Options) => Promise; export declare const register: (program: Command) => void; export {}; diff --git a/dist/commands/installer-hosts.js b/dist/commands/installer-hosts.js index ac95fd21..aab43d41 100644 --- a/dist/commands/installer-hosts.js +++ b/dist/commands/installer-hosts.js @@ -6,10 +6,11 @@ * and return its exit status. Keeping the test, write, and outcome in one * process is what makes an installer success claim useful on both platforms. */ -import { accessSync, constants, existsSync, mkdirSync, renameSync, statSync, unlinkSync, writeFileSync, readFileSync } from 'node:fs'; +import { existsSync, mkdirSync, renameSync, statSync, unlinkSync, writeFileSync, readFileSync } from 'node:fs'; import { delimiter, dirname, join } from 'node:path'; import { randomUUID } from 'node:crypto'; -import { spawn, spawnSync } from 'node:child_process'; +import { spawnSync } from 'node:child_process'; +import { isMcpProbeFailure, probeMcp } from '../core/mcp-probe.js'; import { runtimeIdentity } from '../core/runtime-identity.js'; export const INSTALLER_HOSTS_SCHEMA = 'commitlore_installer_hosts.v1'; const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value); @@ -57,89 +58,6 @@ const atomicJsonWrite = (path, value) => { catch { /* renamed or never created */ } } }; -const executable = (command) => { - try { - if (statSync(command).isDirectory()) - return 'command is a directory'; - accessSync(command, constants.X_OK); - return null; - } - catch { - return 'command does not exist or is not executable'; - } -}; -/** Speak enough MCP to distinguish an executable from a usable CommitLore server. */ -export const probeMcp = async (command, args) => { - const unusable = executable(command); - if (unusable !== null) - return unusable; - return new Promise((resolve) => { - let settled = false; - const finish = (problem) => { - if (settled) - return; - settled = true; - child?.kill(); - resolve(problem); - }; - let child; - try { - child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false }); - } - catch (error) { - finish(`could not start command: ${error instanceof Error ? error.message : String(error)}`); - return; - } - let buffer = ''; - let initialized = false; - const timer = setTimeout(() => finish('MCP initialize timed out'), 5_000); - child.once('error', (error) => finish(`could not start command: ${error.message}`)); - child.once('exit', (code) => { - if (!settled) - finish(`command exited before MCP verification (status ${String(code)})`); - }); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (chunk) => { - buffer += chunk; - const lines = buffer.split('\n'); - buffer = lines.pop() ?? ''; - for (const line of lines) { - let message; - try { - message = JSON.parse(line); - } - catch { - continue; - } - if (!initialized && message.id === 1) { - const info = message.result?.serverInfo; - if (info?.name !== 'commitlore' || typeof info.version !== 'string' || info.version === '') { - clearTimeout(timer); - finish('MCP initialize did not identify CommitLore with a version'); - return; - } - initialized = true; - child?.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })}\n`); - } - else if (initialized && message.id === 2) { - const tools = new Set((message.result?.tools ?? []).map((tool) => tool.name)); - clearTimeout(timer); - finish(tools.has('commitlore_query') && tools.has('commitlore_before_change') ? null : 'MCP server lacks CommitLore minimum tools'); - return; - } - } - }); - // A command that is not an MCP server usually proves it by exiting at once, - // and then this write lands on a closed pipe. Node delivers EPIPE as an - // `error` event on the stream, and an unhandled one takes the whole - // inspector down with it: the installer then exits non-zero having said - // nothing about any host — a worse answer than the `unhealthy` it was one - // step away from giving. Whether the write or the child's exit wins is a - // race, which is why this survived locally and died under CI load. - child.stdin.on('error', () => finish('command closed its input before MCP verification')); - child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'commitlore-installer', version: '1' } } })}\n`); - }); -}; const jsonHost = async (host, path, format, wrapper) => { let config; let existed = true; @@ -168,8 +86,8 @@ const jsonHost = async (host, path, format, wrapper) => { if (launch === null) return { host, requested: true, outcome: 'failed', healthy: false, detail: 'commitlore registration has no runnable command and args' }; const problem = await probeMcp(launch.command, launch.args); - if (problem !== null) - return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem}` }; + if (isMcpProbeFailure(problem)) + return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem.detail}` }; return { host, requested: true, outcome: ownEntry(format, entry, wrapper) ? 'owned' : 'custom-preserved', healthy: true, detail: ownEntry(format, entry, wrapper) ? 'healthy installer-owned registration' : 'healthy custom registration preserved' }; } config[key] = { ...group, commitlore: entryFor(format, wrapper) }; @@ -180,9 +98,9 @@ const jsonHost = async (host, path, format, wrapper) => { return { host, requested: true, outcome: 'failed', healthy: false, detail: `atomic config write failed: ${error instanceof Error ? error.message : String(error)}` }; } const problem = await probeMcp(wrapper, ['mcp']); - return problem === null + return !isMcpProbeFailure(problem) ? { host, requested: true, outcome: 'installed', healthy: true, detail: existed ? 'registration added and live-verified' : 'registration created and live-verified' } - : { host, requested: true, outcome: 'failed', healthy: false, detail: `registration was written but is unhealthy: ${problem}` }; + : { host, requested: true, outcome: 'failed', healthy: false, detail: `registration was written but is unhealthy: ${problem.detail}` }; }; const tomlRegistration = (source) => { const table = /^\s*\[mcp_servers\.commitlore\]\s*$/m.exec(source); @@ -218,8 +136,8 @@ const tomlHost = async (path, wrapper) => { } if (existing !== null) { const problem = await probeMcp(existing.command, existing.args); - if (problem !== null) - return { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem}` }; + if (isMcpProbeFailure(problem)) + return { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem.detail}` }; return { host: 'codex', requested: true, outcome: existing.command === wrapper ? 'owned' : 'custom-preserved', healthy: true, detail: existing.command === wrapper ? 'healthy installer-owned registration' : 'healthy custom registration preserved' }; } const escaped = JSON.stringify(wrapper); @@ -245,9 +163,9 @@ const tomlHost = async (path, wrapper) => { return { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `atomic config write failed: ${String(error)}` }; } const problem = await probeMcp(wrapper, ['mcp']); - return problem === null + return !isMcpProbeFailure(problem) ? { host: 'codex', requested: true, outcome: 'installed', healthy: true, detail: 'Codex config fallback added and live-verified' } - : { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem}` }; + : { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem.detail}` }; }; const hasCommand = (command) => (process.env.PATH ?? '').split(delimiter).some((directory) => { const path = join(directory, command); @@ -272,8 +190,8 @@ const cliHost = async (host, wrapper) => { return { host, requested: true, outcome: 'failed', healthy: false, detail: 'codex CLI returned an unverifiable registration' }; } const problem = await probeMcp(command, args); - if (problem !== null) - return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem}` }; + if (isMcpProbeFailure(problem)) + return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem.detail}` }; return { host, requested: true, outcome: command === wrapper && args.length === 1 && args[0] === 'mcp' ? 'owned' : 'custom-preserved', healthy: true, detail: command === wrapper && args.length === 1 && args[0] === 'mcp' ? 'healthy installer-owned registration' : 'healthy custom registration preserved' }; } // A non-zero `get` with no registration is the Codex CLI's ordinary absence @@ -282,9 +200,9 @@ const cliHost = async (host, wrapper) => { if (!added.ok) return { host, requested: true, outcome: 'failed', healthy: false, detail: 'codex mcp add failed' }; const problem = await probeMcp(wrapper, ['mcp']); - return problem === null + return !isMcpProbeFailure(problem) ? { host, requested: true, outcome: 'installed', healthy: true, detail: 'Codex registration added and live-verified' } - : { host, requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem}` }; + : { host, requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem.detail}` }; }; export const inspectAndApplyHosts = async (options) => { const requested = []; diff --git a/dist/commands/installer-hosts.js.map b/dist/commands/installer-hosts.js.map index 7aa19fa5..95f0e1ea 100644 --- a/dist/commands/installer-hosts.js.map +++ b/dist/commands/installer-hosts.js.map @@ -1 +1 @@ -{"version":3,"file":"installer-hosts.js","sourceRoot":"","sources":["../../src/commands/installer-hosts.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"installer-hosts.js","sourceRoot":"","sources":["../../src/commands/installer-hosts.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/commitlore.mjs b/dist/commitlore.mjs index b20cdd2f..00c8c210 100755 --- a/dist/commitlore.mjs +++ b/dist/commitlore.mjs @@ -12327,9 +12327,9 @@ var verifyDraft = (draft, sources) => { const accepted = []; const rejected = []; for (const record2 of draft) { - const failure4 = unsupportedVerified(record2) ?? missingEvidence(record2) ?? unfoundEvidence(record2, scanned) ?? ungroundedRuledOut(record2, scanned) ?? invalid(record2); - if (failure4 === null) accepted.push({ record: record2 }); - else rejected.push(failure4); + const failure5 = unsupportedVerified(record2) ?? missingEvidence(record2) ?? unfoundEvidence(record2, scanned) ?? ungroundedRuledOut(record2, scanned) ?? invalid(record2); + if (failure5 === null) accepted.push({ record: record2 }); + else rejected.push(failure5); } return { accepted, rejected }; }; @@ -17784,7 +17784,7 @@ var register3 = (program3) => { import { execFileSync } from "node:child_process"; import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9 } from "node:fs"; import { tmpdir } from "node:os"; -import { dirname as dirname8, join as join13, resolve as resolve17 } from "node:path"; +import { dirname as dirname8, join as join14, resolve as resolve17 } from "node:path"; // src/demo/fixture.ts var targetPath = "src/pricing.ts"; @@ -18135,8 +18135,8 @@ var defaultDoctorContext = (opts = {}) => ({ // src/commands/doctor/checks/delivery-inject-runtime.ts var evaluateInjectRun = (run, ctx) => { - const { id, category, title, executable: executable2, path: path2, fix, unavailableFix } = ctx; - const executionEvidence = { executable: executable2, path: path2 }; + const { id, category, title, executable, path: path2, fix, unavailableFix } = ctx; + const executionEvidence = { executable, path: path2 }; if (run.status === null || run.status === void 0) { if (run.error !== void 0 && "code" in run.error && run.error.code === "ENOENT") { return check( @@ -18144,7 +18144,7 @@ var evaluateInjectRun = (run, ctx) => { category, title, "fail", - `configured PreToolUse hook executable ${JSON.stringify(executable2)} is not resolvable from PATH`, + `configured PreToolUse hook executable ${JSON.stringify(executable)} is not resolvable from PATH`, unavailableFix, false, void 0, @@ -18347,9 +18347,9 @@ var checkInjectRuntime = (ctx) => { tool_input: { file_path: resolve5(cwd, path2) } }); const configured = command.replace(` ${CLAUDE_HOOK_MARKER}`, ""); - const executable2 = configured.slice(0, configured.indexOf(" ")); - const args = configured.slice(executable2.length + 1).split(" "); - const run = spawn2(executable2, args, { + const executable = configured.slice(0, configured.indexOf(" ")); + const args = configured.slice(executable.length + 1).split(" "); + const run = spawn2(executable, args, { shell: false, encoding: "utf8", cwd, @@ -18359,7 +18359,7 @@ var checkInjectRuntime = (ctx) => { HOME: env["HOME"] ?? "" } }); - const result = evaluateInjectRun(run, { id, category, title, executable: executable2, path: path2, fix, unavailableFix }); + const result = evaluateInjectRun(run, { id, category, title, executable, path: path2, fix, unavailableFix }); if (result.status === "fail" && run.status === null && run.error !== void 0 && "code" in run.error && run.error.code === "ENOENT") { return { ...result, needsAttention: false }; } @@ -19290,6 +19290,10 @@ var isJsonObject = (value) => typeof value === "object" && value !== null && !Ar var messageOf4 = (error2) => error2 instanceof Error ? error2.message : String(error2); var isLaunchableEntry = (value) => isJsonObject(value) && typeof value["command"] === "string" && value["command"].trim() !== ""; var registeredMcpCommand = (cwd) => { + const launch = registeredMcpLaunch(cwd); + return launch?.command ?? null; +}; +var registeredMcpLaunch = (cwd) => { const path2 = mcpRegistrationPath(cwd); if (path2 === null) return null; let parsed; @@ -19303,7 +19307,9 @@ var registeredMcpCommand = (cwd) => { if (!isJsonObject(servers)) return null; const entry = servers[MCP_SERVER_KEY]; if (!isLaunchableEntry(entry)) return null; - return String(entry["command"]); + const args = entry["args"]; + if (args !== void 0 && (!Array.isArray(args) || !args.every((arg) => typeof arg === "string"))) return null; + return { command: String(entry["command"]), args: args ?? [] }; }; var registrationIsOurs = (cwd) => registeredMcpCommand(cwd) === MCP_SERVER_COMMAND; var holdsLaunchableRegistration = (servers) => isJsonObject(servers) && Object.hasOwn(servers, MCP_SERVER_KEY) && isLaunchableEntry(servers[MCP_SERVER_KEY]); @@ -19560,6 +19566,202 @@ var registerCommitloreMcpServer = (cwd) => { } }; +// src/core/mcp-probe.ts +import { accessSync, constants, statSync as statSync5 } from "node:fs"; +import { spawn, spawnSync as spawnSync5 } from "node:child_process"; +import { delimiter, isAbsolute as isAbsolute2, join as join8 } from "node:path"; +var failure2 = (reason, detail, cleanup) => ({ kind: "failure", reason, detail, ...cleanup === void 0 ? {} : { cleanup } }); +var MCP_READ_TOOLS = ["commitlore_query", "commitlore_before_change"]; +var MCP_CAPTURE_TOOLS = [ + "commitlore_prepare_capture", + "commitlore_verify_capture", + "commitlore_stage_capture" +]; +var isMcpProbeFailure = (result) => result.kind === "failure"; +var CLEANUP_GRACE_MS = 250; +var DEFAULT_INITIALIZE_TIMEOUT_MS = 15e3; +var initializeTimeoutMs = () => { + const raw = Number(process.env["COMMITLORE_MCP_PROBE_TIMEOUT_MS"]); + return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INITIALIZE_TIMEOUT_MS; +}; +var wait = (milliseconds) => new Promise((resolve22) => setTimeout(resolve22, milliseconds)); +var stopProbeChild = async (child) => { + if (child.exitCode !== null || child.signalCode !== null) return "not-needed"; + let exitedResolve; + const exited = new Promise((resolve22) => { + exitedResolve = resolve22; + }); + child.once("exit", () => exitedResolve?.()); + if (process.platform === "win32") { + if (child.pid === void 0) return "could-not-reclaim"; + const result = spawnSync5("taskkill", ["/PID", String(child.pid), "/T", "/F"], { + stdio: "ignore", + windowsHide: true + }); + if (result.error !== void 0 || result.status !== 0) return "could-not-reclaim"; + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + return "reclaimed"; + } + const signal = (value) => { + if (child.pid !== void 0) { + try { + process.kill(-child.pid, value); + return; + } catch { + } + } + child.kill(value); + }; + signal("SIGTERM"); + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + if (child.exitCode === null && child.signalCode === null) { + signal("SIGKILL"); + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + } + return child.exitCode === null && child.signalCode === null ? "could-not-reclaim" : "reclaimed"; +}; +var commandPath = (command) => { + const bare = !isAbsolute2(command) && !command.includes("/"); + const pathCandidates = bare ? (process.env["PATH"] ?? "").split(delimiter).filter(Boolean).map((directory) => join8(directory, command)) : [command]; + const hasExtension = command.lastIndexOf(".") > command.lastIndexOf("/"); + const extensions = process.platform === "win32" && bare && !hasExtension ? (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : []; + const candidates = pathCandidates.flatMap((candidate) => [ + candidate, + ...extensions.map((extension) => candidate + extension) + ]); + let nonExecutable; + for (const candidate of candidates) { + try { + if (statSync5(candidate).isDirectory()) return failure2("command-is-directory", "command is a directory"); + accessSync(candidate, constants.X_OK); + return candidate; + } catch (error2) { + if (error2.code !== "ENOENT") { + nonExecutable ??= failure2("command-not-executable", "command is not executable"); + } + } + } + return nonExecutable ?? failure2("command-not-found", "command does not exist"); +}; +var needsWindowsCommandShell = (command) => process.platform === "win32" && /\.(?:cmd|bat)$/i.test(command); +var probeMcp = async (command, args) => { + const resolved = commandPath(command); + if (typeof resolved !== "string") return resolved; + return new Promise((resolve22) => { + let settled = false; + let child; + let timer; + const finish = (problem) => { + if (settled) return; + settled = true; + if (timer !== void 0) clearTimeout(timer); + void (async () => { + const cleanup = child === void 0 ? "not-needed" : await stopProbeChild(child); + if (cleanup === "could-not-reclaim") { + resolve22(failure2("probe-unavailable", "MCP verification completed but could not reclaim its child process tree", cleanup)); + return; + } + resolve22({ ...problem, cleanup }); + })(); + }; + try { + const childEnv = { ...process.env }; + delete childEnv["COMMITLORE_MCP_PROBE"]; + child = spawn(resolved, args, { + stdio: ["pipe", "pipe", "pipe"], + shell: needsWindowsCommandShell(resolved), + env: childEnv, + detached: process.platform !== "win32" + }); + } catch (error2) { + finish(failure2("command-could-not-start", `could not start command: ${error2 instanceof Error ? error2.message : String(error2)}`)); + return; + } + let buffer = ""; + let initialized = false; + const timeoutMs = initializeTimeoutMs(); + timer = setTimeout( + () => finish(failure2("initialize-timed-out", `MCP initialize timed out after ${timeoutMs}ms`)), + timeoutMs + ); + child.once("error", (error2) => finish(failure2("command-could-not-start", `could not start command: ${error2.message}`))); + child.once("exit", (code) => { + if (!settled) finish(failure2("command-exited", `command exited before MCP verification (status ${String(code)})`)); + }); + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + buffer += chunk; + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line2 of lines) { + let message; + try { + message = JSON.parse(line2); + } catch { + continue; + } + if (!initialized && message.id === 1) { + const info = message.result?.serverInfo; + if (info?.name !== "commitlore" || typeof info.version !== "string" || info.version === "") { + finish(failure2("foreign-server", "MCP initialize did not identify CommitLore with a version")); + return; + } + initialized = true; + child?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })} +`); + } else if (initialized && message.id === 2) { + const tools = new Set((message.result?.tools ?? []).map((tool) => tool.name)); + if (!MCP_READ_TOOLS.every((tool) => tools.has(tool))) { + finish(failure2("missing-tools", "MCP server lacks CommitLore read-delivery tools")); + } else if (MCP_CAPTURE_TOOLS.every((tool) => tools.has(tool))) { + finish({ kind: "capture-initiator", detail: "MCP server advertises CommitLore read and capture tools", cleanup: "not-needed" }); + } else { + finish({ kind: "read-delivery", detail: "MCP server advertises CommitLore read tools but not the complete capture tool set", cleanup: "not-needed" }); + } + return; + } + } + }); + child.stdin.on("error", () => finish(failure2("command-closed-input", "command closed its input before MCP verification"))); + setTimeout(() => { + child?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "commitlore-probe", version: "1" } } })} +`); + }, 200); + }); +}; +var probeMcpSync = (command, args) => { + const result = spawnSync5(process.execPath, [ + installedPath("dist", "commitlore.mjs"), + "internal", + "mcp-probe", + "--command", + command, + "--args-json", + JSON.stringify(args) + ], { + encoding: "utf8", + // The helper owns the protocol timeout and needs room beyond it to reap a + // stubborn server. This outer bound is only a safety net for a broken + // helper, not the server-response timeout we report — so it is derived + // from that budget rather than written next to it, because a constant that + // silently fell below it would kill the helper before it could answer and + // report the death as the server's fault. + timeout: initializeTimeoutMs() + 2e3, + env: { ...process.env, COMMITLORE_MCP_PROBE: "1" } + }); + if (result.error !== void 0) { + return failure2("probe-unavailable", `could not run MCP verification: ${result.error.message}`); + } + try { + const parsed = JSON.parse(result.stdout); + if (typeof parsed === "object" && parsed !== null && typeof parsed.kind === "string" && typeof parsed.detail === "string" && (parsed.kind === "read-delivery" || parsed.kind === "capture-initiator" || parsed.kind === "failure" && typeof parsed.reason === "string")) { + return parsed; + } + } catch { + } + return failure2("probe-unavailable", `could not read MCP verification result (status ${String(result.status)})`); +}; + // src/commands/doctor/checks/capture-unattended-initiator.ts var checkUnattendedCaptureInitiator = (ctx) => { const id = "unattended-initiator"; @@ -19605,25 +19807,51 @@ var checkUnattendedCaptureInitiator = (ctx) => { } ); } - if (registersCommitloreMcpServer(cwd)) { + const launch = registeredMcpLaunch(cwd); + if (launch !== null && registersCommitloreMcpServer(cwd)) { const command = registeredMcpCommand(cwd); const ours = registrationIsOurs(cwd); - if (!ours) { + const probe = probeMcpSync(launch.command, launch.args); + if (isMcpProbeFailure(probe)) { + const unverified = probe.reason === "initialize-timed-out"; + return check( + id, + category, + title, + "warn", + unverified ? `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, and it did not answer in time to be verified: ${probe.detail}. This does not say the registration is broken \u2014 a cold start on a loaded machine can outlast the probe.` : `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, but it is unhealthy: ${probe.detail}`, + unverified ? `rerun commitlore doctor when the machine is quieter; if it keeps timing out, start ${JSON.stringify(command)} by hand and check that it answers an MCP initialize` : `repair ${JSON.stringify(command)} so it answers as a CommitLore MCP server, or remove the entry and run commitlore init`, + false, + void 0, + { + evidence: { + policy: "unattended", + ordinary_git_commit: "cannot-initiate", + initiator: unverified ? "registered-command-unverified" : "registered-command-unhealthy", + command: command ?? "", + probe: probe.reason + } + } + ); + } + if (probe.kind === "read-delivery") { return check( id, category, title, "warn", - `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, which is not the command this tool writes \u2014 it is left alone, and whether it starts a capture server is unverified`, - `check that ${JSON.stringify(command)} is a CommitLore MCP server, or remove the entry and run commitlore init`, + `${MCP_REGISTRATION_FILE} registers a live CommitLore read-delivery server${ours ? "" : " through a custom wrapper"}, but it does not advertise all three capture tools, so it is not an unattended capture initiator`, + "register a CommitLore MCP server that advertises commitlore_prepare_capture, commitlore_verify_capture, and commitlore_stage_capture", false, void 0, { evidence: { policy: "unattended", ordinary_git_commit: "cannot-initiate", - initiator: "registered-command-unverified", - command: command ?? "" + initiator: "read-delivery-only", + registration: ours ? "installer-owned" : "custom-preserved", + verified: "identity-and-read-tools", + capture_tools: "not-complete" } } ); @@ -19633,7 +19861,7 @@ var checkUnattendedCaptureInitiator = (ctx) => { category, title, "ok", - `${MCP_REGISTRATION_FILE} registers the capture server, so a host loading it can start unattended capture; an ordinary git commit outside that host still cannot`, + `${MCP_REGISTRATION_FILE} registers a live CommitLore MCP server${ours ? "" : " through a custom wrapper"} that advertises the required read and capture tools; this verifies identity and tool set, not asset readiness \u2014 required assets such as SPEC.md can still be missing and make prepare fail; an ordinary git commit outside that host still cannot initiate capture`, null, false, void 0, @@ -19641,10 +19869,10 @@ var checkUnattendedCaptureInitiator = (ctx) => { evidence: { policy: "unattended", ordinary_git_commit: "cannot-initiate", - initiator: "mcp-server-registered", - // Registration is configuration, not observation: nothing here - // proves a host has ever called the tool. - verified: "registration-only" + initiator: "capture-tools-advertised", + registration: ours ? "installer-owned" : "custom-preserved", + verified: "identity-and-read-and-capture-tools", + asset_readiness: "not-verified" } } ); @@ -19717,8 +19945,8 @@ var checkInjectVersion = (ctx, dependencies) => { ); } const configured = command.replace(` ${CLAUDE_HOOK_MARKER}`, ""); - const executable2 = configured.slice(0, configured.indexOf(" ")); - const run = spawn2(executable2, ["--version"], { + const executable = configured.slice(0, configured.indexOf(" ")); + const run = spawn2(executable, ["--version"], { shell: false, encoding: "utf8", cwd, @@ -19729,7 +19957,7 @@ var checkInjectVersion = (ctx, dependencies) => { }); const reported = typeof run.stdout === "string" ? run.stdout : ""; const versionEvidence = { - executable: executable2, + executable, theirs: boundedExcerpt(reported).firstLine || "unavailable", mine, exit_code: String(run.status ?? "unavailable"), @@ -19741,7 +19969,7 @@ var checkInjectVersion = (ctx, dependencies) => { category, title, "skipped", - `${executable2} did not report a version`, + `${executable} did not report a version`, null, false, false, @@ -19757,7 +19985,7 @@ var checkInjectVersion = (ctx, dependencies) => { category, title, "skipped", - `${executable2} answered --version with something that is not a version`, + `${executable} answered --version with something that is not a version`, null, false, false, @@ -19835,19 +20063,19 @@ var checkDirectiveTrustMode = (ctx) => { }; // src/mcp/lifecycle.ts -import { appendFileSync, mkdirSync as mkdirSync4, readFileSync as readFileSync12, statSync as statSync5, writeFileSync as writeFileSync7, writeSync } from "node:fs"; -import { dirname as dirname6, join as join8, resolve as resolve10 } from "node:path"; +import { appendFileSync, mkdirSync as mkdirSync4, readFileSync as readFileSync12, statSync as statSync6, writeFileSync as writeFileSync7, writeSync } from "node:fs"; +import { dirname as dirname6, join as join9, resolve as resolve10 } from "node:path"; var MAX_BYTES = 64 * 1024; var LIFECYCLE_FILE = "mcp-lifecycle.log"; var lifecyclePath = (cwd = process.cwd()) => { - const result = execGit(["rev-parse", "--git-path", join8("commitlore", LIFECYCLE_FILE)], { cwd }); + const result = execGit(["rev-parse", "--git-path", join9("commitlore", LIFECYCLE_FILE)], { cwd }); if (result.code !== 0) return null; const path2 = result.stdout.trim(); return path2 === "" ? null : resolve10(cwd, path2); }; var trim = (path2) => { try { - if (statSync5(path2).size <= MAX_BYTES) return; + if (statSync6(path2).size <= MAX_BYTES) return; const lines = readFileSync12(path2, "utf8").split("\n"); writeFileSync7(path2, `${lines.slice(Math.floor(lines.length / 2)).join("\n")}`); } catch { @@ -20667,11 +20895,11 @@ var checkRuntime = (ctx) => { // src/commands/doctor/checks/runtime-runtime-identity.ts import { existsSync as existsSync12 } from "node:fs"; -import { join as join9 } from "node:path"; +import { join as join10 } from "node:path"; var pluginIdentity = () => { const root = process.env["CLAUDE_PLUGIN_ROOT"]; if (root === void 0 || root === "") return void 0; - const entry = join9(root, "dist", "commitlore.mjs"); + const entry = join10(root, "dist", "commitlore.mjs"); try { return existsSync12(entry) ? runtimeIdentity(entry) : void 0; } catch { @@ -21200,7 +21428,7 @@ ${formatCheckReport(report, options)}`; // src/commands/doctor/report.ts import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs"; -import { join as join10, resolve as resolve11, sep as sep2 } from "node:path"; +import { join as join11, resolve as resolve11, sep as sep2 } from "node:path"; // src/commands/doctor/runner.ts var containedRun = (definition, ctx, dependencies) => { @@ -21311,8 +21539,8 @@ var deriveInstallSource = ({ if (segments.includes("_npx")) return "npx"; if (segments.includes("node_modules")) return "npm"; try { - const manifest = JSON.parse(readFileSync13(join10(packageRoot, "package.json"), "utf8")); - if (manifest.name === "commitlore" && existsSync13(join10(packageRoot, ".git"))) return "source"; + const manifest = JSON.parse(readFileSync13(join11(packageRoot, "package.json"), "utf8")); + if (manifest.name === "commitlore" && existsSync13(join11(packageRoot, ".git"))) return "source"; } catch { } return "unknown"; @@ -21383,11 +21611,11 @@ import { readFileSync as readFileSync17, realpathSync as realpathSync3, renameSync as renameSync7, - statSync as statSync6, + statSync as statSync7, unlinkSync as unlinkSync5, writeFileSync as writeFileSync11 } from "node:fs"; -import { join as join11, resolve as resolve15 } from "node:path"; +import { join as join12, resolve as resolve15 } from "node:path"; // src/hooks/post-commit.ts import { createHash as createHash6, randomBytes as randomBytes5 } from "node:crypto"; @@ -21570,7 +21798,7 @@ var revParse2 = (ref, opts) => { return result.code === 0 && sha !== "" ? sha : null; }; var isAncestor = (a, b, opts) => execGit(["merge-base", "--is-ancestor", a, b], gitOptions4(opts)).code === 0; -var failure2 = (remote, detail) => ({ +var failure3 = (remote, detail) => ({ remote, outcome: "failed", detail @@ -21582,7 +21810,7 @@ var syncRemote = (remote, opts = {}) => { ); const remoteMissing = fetched.code !== 0 && /couldn't find remote ref|does not appear to be a git repository/i.test(fetched.stderr); if (fetched.code !== 0 && !remoteMissing) { - return failure2(remote, fetched.stderr.trim() || `git fetch ${remote} failed`); + return failure3(remote, fetched.stderr.trim() || `git fetch ${remote} failed`); } const local = revParse2(NOTES_REF, opts); const theirs = remoteMissing ? null : revParse2(FETCH_HEAD_REF, opts); @@ -21594,7 +21822,7 @@ var syncRemote = (remote, opts = {}) => { return { remote, outcome: "fetched", detail: "would collect the remote mirror" }; } const updated = execGit(["update-ref", NOTES_REF, theirs], gitOptions4(opts)); - return updated.code === 0 ? { remote, outcome: "fetched", detail: "collected the remote mirror" } : failure2(remote, updated.stderr.trim() || "could not update the local notes ref"); + return updated.code === 0 ? { remote, outcome: "fetched", detail: "collected the remote mirror" } : failure3(remote, updated.stderr.trim() || "could not update the local notes ref"); } if (local !== null && theirs !== null) { if (local === theirs) return { remote, outcome: "in-sync", detail: "" }; @@ -21603,7 +21831,7 @@ var syncRemote = (remote, opts = {}) => { return { remote, outcome: "fetched", detail: "would fast-forward to the remote mirror" }; } const updated = execGit(["update-ref", NOTES_REF, theirs], gitOptions4(opts)); - return updated.code === 0 ? { remote, outcome: "fetched", detail: "fast-forwarded to the remote mirror" } : failure2(remote, updated.stderr.trim() || "could not update the local notes ref"); + return updated.code === 0 ? { remote, outcome: "fetched", detail: "fast-forwarded to the remote mirror" } : failure3(remote, updated.stderr.trim() || "could not update the local notes ref"); } if (!isAncestor(theirs, local, opts)) { if (opts.dryRun === true) { @@ -21624,7 +21852,7 @@ var syncRemote = (remote, opts = {}) => { return { remote, outcome: "merged", detail: "merged both mirrors; not published" }; } const pushed2 = pushMirror(remote, opts); - return pushed2.code === 0 ? { remote, outcome: "merged", detail: "merged both mirrors and published" } : failure2(remote, pushed2.stderr.trim() || `git push ${remote} failed`); + return pushed2.code === 0 ? { remote, outcome: "merged", detail: "merged both mirrors and published" } : failure3(remote, pushed2.stderr.trim() || `git push ${remote} failed`); } } if (opts.fetchOnly === true) { @@ -21634,7 +21862,7 @@ var syncRemote = (remote, opts = {}) => { return { remote, outcome: "pushed", detail: "would publish the local mirror" }; } const pushed = pushMirror(remote, opts); - return pushed.code === 0 ? { remote, outcome: "pushed", detail: "published the local mirror" } : failure2(remote, pushed.stderr.trim() || `git push ${remote} failed`); + return pushed.code === 0 ? { remote, outcome: "pushed", detail: "published the local mirror" } : failure3(remote, pushed.stderr.trim() || `git push ${remote} failed`); }; var syncNotes = (opts = {}) => { const remotes = opts.remotes ?? listRemotes(opts); @@ -21945,7 +22173,7 @@ var register8 = (program3) => { // src/commands/hooks.ts var messageOf5 = (error2) => error2 instanceof Error ? error2.message : String(error2); var firstLine3 = (text) => (text.trim().split("\n")[0] ?? "").trim(); -var failure3 = (message) => ({ +var failure4 = (message) => ({ code: 2, stdout: "", stderr: `commitlore: ${message} @@ -21967,7 +22195,7 @@ var resolveHooksDir = (cwd) => { }; var isExecutable = (path2) => { try { - return (statSync6(path2).mode & 73) !== 0; + return (statSync7(path2).mode & 73) !== 0; } catch { return false; } @@ -21985,8 +22213,8 @@ var readHookState = (hookPath) => { }; var readHookStatus = (cwd = process.cwd()) => { const hooksDir = resolveHooksDir(cwd); - const hookPath = join11(hooksDir, HOOK_NAME); - const chainedPath = join11(hooksDir, CHAINED_HOOK_NAME); + const hookPath = join12(hooksDir, HOOK_NAME); + const chainedPath = join12(hooksDir, CHAINED_HOOK_NAME); return { hooksDir, hookPath, @@ -22007,7 +22235,7 @@ var resolveEntryForRecord = (entry, cwd) => { if (entry === void 0 || entry === "") return null; const existingFile = (candidate) => { try { - return statSync6(candidate).isFile() ? candidate : null; + return statSync7(candidate).isFile() ? candidate : null; } catch { return null; } @@ -22042,12 +22270,12 @@ var installHook = (input = {}) => { mkdirSync8(resolveHooksDir(cwd), { recursive: true }); before = readHookStatus(cwd); } catch (error2) { - return failure3(messageOf5(error2)); + return failure4(messageOf5(error2)); } try { if (before.state === "foreign") { if (before.chained && input.force !== true) { - return failure3( + return failure4( `${before.hookPath} is not a commitlore hook and ${before.chainedPath} already exists \u2014 move one aside, or pass --force to replace the preserved hook` ); } @@ -22056,7 +22284,7 @@ var installHook = (input = {}) => { writeStub(before.hookPath); recordBinPath(cwd); } catch (error2) { - return failure3(`could not install the ${HOOK_NAME} hook: ${messageOf5(error2)}`); + return failure4(`could not install the ${HOOK_NAME} hook: ${messageOf5(error2)}`); } const after = readHookStatus(cwd); const headline = { @@ -22087,8 +22315,8 @@ var CAPTURE_HOOKS = [ } ]; var removeCaptureHook = (hooksDir, hook) => { - const hookPath = join11(hooksDir, hook.name); - const chainedPath = join11(hooksDir, hook.chainedName); + const hookPath = join12(hooksDir, hook.name); + const chainedPath = join12(hooksDir, hook.chainedName); if (!existsSync17(hookPath)) return [`no ${hook.name} hook to remove: ${hookPath}`]; let contents; try { @@ -22110,7 +22338,7 @@ var uninstallHook = (input = {}) => { try { before = readHookStatus(cwd); } catch (error2) { - return failure3(messageOf5(error2)); + return failure4(messageOf5(error2)); } const lines = []; if (before.state === "absent") { @@ -22125,7 +22353,7 @@ var uninstallHook = (input = {}) => { unlinkSync5(before.hookPath); if (before.chained) renameSync7(before.chainedPath, before.hookPath); } catch (error2) { - return failure3(`could not remove the ${HOOK_NAME} hook: ${messageOf5(error2)}`); + return failure4(`could not remove the ${HOOK_NAME} hook: ${messageOf5(error2)}`); } lines.push(`removed ${HOOK_NAME} hook: ${before.hookPath}`); if (before.chained) lines.push(`restored the previous hook: ${before.hookPath}`); @@ -22134,7 +22362,7 @@ var uninstallHook = (input = {}) => { try { lines.push(...removeCaptureHook(before.hooksDir, hook)); } catch (error2) { - return failure3(`could not remove the ${hook.name} hook: ${messageOf5(error2)}`); + return failure4(`could not remove the ${hook.name} hook: ${messageOf5(error2)}`); } } return success2(readHookStatus(cwd), lines); @@ -22145,7 +22373,7 @@ var hookStatus = (input = {}) => { try { status = readHookStatus(cwd); } catch (error2) { - return failure3(messageOf5(error2)); + return failure4(messageOf5(error2)); } const state = { absent: "not installed", @@ -22187,8 +22415,8 @@ var register9 = (program3) => { }; // src/core/agents-guidance.ts -import { existsSync as existsSync18, readFileSync as readFileSync18, renameSync as renameSync8, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync12 } from "node:fs"; -import { basename as basename2, dirname as dirname7, join as join12, resolve as resolve16 } from "node:path"; +import { existsSync as existsSync18, readFileSync as readFileSync18, renameSync as renameSync8, rmSync as rmSync3, statSync as statSync8, writeFileSync as writeFileSync12 } from "node:fs"; +import { basename as basename2, dirname as dirname7, join as join13, resolve as resolve16 } from "node:path"; import { fileURLToPath as fileURLToPath2 } from "node:url"; var AGENTS_SECTION_BEGIN = ""; var AGENTS_SECTION_END = ""; @@ -22209,7 +22437,7 @@ var readCommitloreAgentsSection = () => { }; var markerCount = (contents, marker) => contents.split(marker).length - 1; var replaceFile = (path2, contents) => { - const mode = statSync7(path2).mode & 511; + const mode = statSync8(path2).mode & 511; const temporary = `${path2}.commitlore-incoming-${process.pid}`; try { writeFileSync12(temporary, contents, { mode }); @@ -22223,7 +22451,7 @@ var replaceFile = (path2, contents) => { } }; var installAgentsGuidance = (cwd) => { - const path2 = join12(cwd, "AGENTS.md"); + const path2 = join13(cwd, "AGENTS.md"); let section2; try { section2 = readCommitloreAgentsSection(); @@ -22751,7 +22979,7 @@ var runDemo = async (opts = {}) => { process.prependOnceListener("SIGINT", onSignal); process.prependOnceListener("SIGTERM", onSignal); try { - tmpDir = mkdtempSync(join13(opts.tmpRoot ?? tmpdir(), "commitlore-demo-")); + tmpDir = mkdtempSync(join14(opts.tmpRoot ?? tmpdir(), "commitlore-demo-")); const userCwd = resolve17(opts.cwd ?? process.cwd()); const tmpResolved = resolve17(tmpDir); if (tmpResolved === userCwd || tmpResolved.startsWith(userCwd + "/") || userCwd.startsWith(tmpResolved + "/")) { @@ -22761,7 +22989,7 @@ var runDemo = async (opts = {}) => { git(["config", "user.name", "CommitLore Demo"], tmpDir); git(["config", "user.email", "demo@commitlore.example"], tmpDir); git(["config", "commit.gpgsign", "false"], tmpDir); - const targetFullPath = join13(tmpDir, targetPath); + const targetFullPath = join14(tmpDir, targetPath); mkdirSync9(dirname8(targetFullPath), { recursive: true }); writeFileSync13(targetFullPath, "export const calculatePrice = () => {};\n"); git(["add", "."], tmpDir); @@ -23227,10 +23455,10 @@ var register14 = (program3) => { }; // src/commands/hermes.ts -import { spawnSync as spawnSync5 } from "node:child_process"; -import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync22, renameSync as renameSync9, statSync as statSync8, writeFileSync as writeFileSync16 } from "node:fs"; +import { spawnSync as spawnSync6 } from "node:child_process"; +import { copyFileSync, existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync22, renameSync as renameSync9, statSync as statSync9, writeFileSync as writeFileSync16 } from "node:fs"; import { homedir } from "node:os"; -import { basename as basename3, dirname as dirname9, join as join14, resolve as resolve19 } from "node:path"; +import { basename as basename3, dirname as dirname9, join as join15, resolve as resolve19 } from "node:path"; // src/core/hermes-config.ts import { relative as relative2, resolve as resolve18, sep as sep3 } from "node:path"; @@ -23448,7 +23676,7 @@ var removeHermesConfig = (contents, options) => { // src/commands/hermes.ts var commandExists = (command) => { - const result = spawnSync5(command, ["--version"], { encoding: "utf8", timeout: 5e3, stdio: "ignore" }); + const result = spawnSync6(command, ["--version"], { encoding: "utf8", timeout: 5e3, stdio: "ignore" }); return result.error === void 0; }; var backupPathFor = (configPath) => { @@ -23460,7 +23688,7 @@ var backupPathFor = (configPath) => { } }; var atomicallyWrite = (path2, contents, mode) => { - const temporary = join14(dirname9(path2), `.${basename3(path2)}.commitlore-${process.pid}.tmp`); + const temporary = join15(dirname9(path2), `.${basename3(path2)}.commitlore-${process.pid}.tmp`); try { if (mode === void 0) writeFileSync16(temporary, contents, "utf8"); else writeFileSync16(temporary, contents, { encoding: "utf8", mode }); @@ -23474,7 +23702,7 @@ var runVerification = (report, verified) => { report.push("unverified: Hermes is not on PATH, so start a fresh session to load the configured profile"); return; } - const skills = spawnSync5("hermes", ["skills", "list", "--source", "all"], { + const skills = spawnSync6("hermes", ["skills", "list", "--source", "all"], { encoding: "utf8", timeout: 15e3 }); @@ -23486,7 +23714,7 @@ var runVerification = (report, verified) => { } else { report.push("unverified: Hermes did not list every CommitLore skill; start a fresh session and run `hermes skills list --source all`"); } - const mcp = spawnSync5("hermes", ["mcp", "test", "commitlore"], { + const mcp = spawnSync6("hermes", ["mcp", "test", "commitlore"], { encoding: "utf8", timeout: 15e3 }); @@ -23501,9 +23729,9 @@ var runVerification = (report, verified) => { var runHermesInstall = (options = {}) => { const home = options.home ?? homedir(); const hermesHome = process.env["HERMES_HOME"]; - const configPath = options.configPath ?? (hermesHome === void 0 ? join14(home, ".hermes", "config.yaml") : join14(hermesHome, "config.yaml")); - const dataHome = options.dataHome ?? process.env["XDG_DATA_HOME"] ?? join14(home, ".local", "share"); - const dataRoot = options.dataRoot ?? join14(dataHome, "commitlore"); + const configPath = options.configPath ?? (hermesHome === void 0 ? join15(home, ".hermes", "config.yaml") : join15(hermesHome, "config.yaml")); + const dataHome = options.dataHome ?? process.env["XDG_DATA_HOME"] ?? join15(home, ".local", "share"); + const dataRoot = options.dataRoot ?? join15(dataHome, "commitlore"); const skillsDir = options.skillsDir ?? installedPath("hermes", "skills"); const detected = options.detected ?? (existsSync20(dirname9(configPath)) || commandExists("hermes")); const report = []; @@ -23524,7 +23752,7 @@ var runHermesInstall = (options = {}) => { verified }; } - const wrapperPath = options.wrapperPath ?? join14(home, ".local", "bin", "commitlore"); + const wrapperPath = options.wrapperPath ?? join15(home, ".local", "bin", "commitlore"); const before = existsSync20(configPath) ? readFileSync22(configPath, "utf8") : ""; const edit = addHermesConfig(before, { wrapperPath, @@ -23543,7 +23771,7 @@ var runHermesInstall = (options = {}) => { copyFileSync(configPath, backup); report.push(`backed up: ${configPath} -> ${backup}`); } - const mode = existsSync20(configPath) ? statSync8(configPath).mode : void 0; + const mode = existsSync20(configPath) ? statSync9(configPath).mode : void 0; atomicallyWrite(configPath, edit.contents, mode); report.push(`configured: ${edit.added.join(" and ")} in ${configPath}`); } catch (error2) { @@ -23677,7 +23905,7 @@ var register16 = (program3) => { // src/commands/inject.ts import { readFileSync as readFileSync23, realpathSync as realpathSync4 } from "node:fs"; -import { basename as basename4, dirname as dirname10, isAbsolute as isAbsolute2, join as join15, relative as relative3, resolve as resolve20, sep as sep4 } from "node:path"; +import { basename as basename4, dirname as dirname10, isAbsolute as isAbsolute3, join as join16, relative as relative3, resolve as resolve20, sep as sep4 } from "node:path"; // src/core/inject.ts import { createHash as createHash8 } from "node:crypto"; @@ -24079,7 +24307,7 @@ var canonical = (target) => { for (; ; ) { try { const real = realpathSync4(current); - return tail.length === 0 ? real : join15(real, ...tail); + return tail.length === 0 ? real : join16(real, ...tail); } catch { const parent = dirname10(current); if (parent === current) return absolute; @@ -24104,10 +24332,10 @@ var payloadPath = (payload, cwd) => { } const root = repositoryRoot2(cwd); if (root === void 0) throw new Error("repository root could not be resolved"); - const target = canonical(isAbsolute2(raw) ? raw : resolve20(cwd, raw)); + const target = canonical(isAbsolute3(raw) ? raw : resolve20(cwd, raw)); const scoped = relative3(canonical(root), target); if (scoped === "") throw new Error("file_path resolves to the repository root"); - if (scoped === ".." || scoped.startsWith(`..${sep4}`) || isAbsolute2(scoped)) { + if (scoped === ".." || scoped.startsWith(`..${sep4}`) || isAbsolute3(scoped)) { throw new Error("file_path resolves outside the repository"); } return scoped; @@ -24242,10 +24470,10 @@ var register17 = (program3) => { }; // src/commands/installer-hosts.ts -import { accessSync, constants, existsSync as existsSync21, mkdirSync as mkdirSync11, renameSync as renameSync10, statSync as statSync9, unlinkSync as unlinkSync6, writeFileSync as writeFileSync17, readFileSync as readFileSync24 } from "node:fs"; -import { delimiter, dirname as dirname11, join as join16 } from "node:path"; +import { existsSync as existsSync21, mkdirSync as mkdirSync11, renameSync as renameSync10, statSync as statSync10, unlinkSync as unlinkSync6, writeFileSync as writeFileSync17, readFileSync as readFileSync24 } from "node:fs"; +import { delimiter as delimiter2, dirname as dirname11, join as join17 } from "node:path"; import { randomUUID } from "node:crypto"; -import { spawn, spawnSync as spawnSync6 } from "node:child_process"; +import { spawnSync as spawnSync7 } from "node:child_process"; var INSTALLER_HOSTS_SCHEMA = "commitlore_installer_hosts.v1"; var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value); var ownEntry = (format, entry, wrapper) => { @@ -24268,7 +24496,7 @@ var commandOf = (format, entry) => { var entryFor = (format, wrapper) => format === "json-mcp" ? { type: "local", command: [wrapper, "mcp"], enabled: true } : { command: wrapper, args: ["mcp"] }; var atomicJsonWrite = (path2, value) => { mkdirSync11(dirname11(path2), { recursive: true }); - const temporary = join16(dirname11(path2), `.${String(path2.split("/").pop())}.commitlore-${process.pid}-${randomUUID()}.tmp`); + const temporary = join17(dirname11(path2), `.${String(path2.split("/").pop())}.commitlore-${process.pid}-${randomUUID()}.tmp`); try { writeFileSync17(temporary, `${JSON.stringify(value, null, 2)} `, { encoding: "utf8", mode: 384 }); @@ -24284,75 +24512,6 @@ var atomicJsonWrite = (path2, value) => { } } }; -var executable = (command) => { - try { - if (statSync9(command).isDirectory()) return "command is a directory"; - accessSync(command, constants.X_OK); - return null; - } catch { - return "command does not exist or is not executable"; - } -}; -var probeMcp = async (command, args) => { - const unusable = executable(command); - if (unusable !== null) return unusable; - return new Promise((resolve22) => { - let settled = false; - const finish = (problem) => { - if (settled) return; - settled = true; - child?.kill(); - resolve22(problem); - }; - let child; - try { - child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"], shell: false }); - } catch (error2) { - finish(`could not start command: ${error2 instanceof Error ? error2.message : String(error2)}`); - return; - } - let buffer = ""; - let initialized = false; - const timer = setTimeout(() => finish("MCP initialize timed out"), 5e3); - child.once("error", (error2) => finish(`could not start command: ${error2.message}`)); - child.once("exit", (code) => { - if (!settled) finish(`command exited before MCP verification (status ${String(code)})`); - }); - child.stdout.setEncoding("utf8"); - child.stdout.on("data", (chunk) => { - buffer += chunk; - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - for (const line2 of lines) { - let message; - try { - message = JSON.parse(line2); - } catch { - continue; - } - if (!initialized && message.id === 1) { - const info = message.result?.serverInfo; - if (info?.name !== "commitlore" || typeof info.version !== "string" || info.version === "") { - clearTimeout(timer); - finish("MCP initialize did not identify CommitLore with a version"); - return; - } - initialized = true; - child?.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })} -`); - } else if (initialized && message.id === 2) { - const tools = new Set((message.result?.tools ?? []).map((tool) => tool.name)); - clearTimeout(timer); - finish(tools.has("commitlore_query") && tools.has("commitlore_before_change") ? null : "MCP server lacks CommitLore minimum tools"); - return; - } - } - }); - child.stdin.on("error", () => finish("command closed its input before MCP verification")); - child.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "commitlore-installer", version: "1" } } })} -`); - }); -}; var jsonHost = async (host, path2, format, wrapper) => { let config3; let existed = true; @@ -24377,7 +24536,7 @@ var jsonHost = async (host, path2, format, wrapper) => { const launch = commandOf(format, entry); if (launch === null) return { host, requested: true, outcome: "failed", healthy: false, detail: "commitlore registration has no runnable command and args" }; const problem2 = await probeMcp(launch.command, launch.args); - if (problem2 !== null) return { host, requested: true, outcome: "failed", healthy: false, detail: `existing registration is unhealthy: ${problem2}` }; + if (isMcpProbeFailure(problem2)) return { host, requested: true, outcome: "failed", healthy: false, detail: `existing registration is unhealthy: ${problem2.detail}` }; return { host, requested: true, outcome: ownEntry(format, entry, wrapper) ? "owned" : "custom-preserved", healthy: true, detail: ownEntry(format, entry, wrapper) ? "healthy installer-owned registration" : "healthy custom registration preserved" }; } config3[key] = { ...group, commitlore: entryFor(format, wrapper) }; @@ -24387,7 +24546,7 @@ var jsonHost = async (host, path2, format, wrapper) => { return { host, requested: true, outcome: "failed", healthy: false, detail: `atomic config write failed: ${error2 instanceof Error ? error2.message : String(error2)}` }; } const problem = await probeMcp(wrapper, ["mcp"]); - return problem === null ? { host, requested: true, outcome: "installed", healthy: true, detail: existed ? "registration added and live-verified" : "registration created and live-verified" } : { host, requested: true, outcome: "failed", healthy: false, detail: `registration was written but is unhealthy: ${problem}` }; + return !isMcpProbeFailure(problem) ? { host, requested: true, outcome: "installed", healthy: true, detail: existed ? "registration added and live-verified" : "registration created and live-verified" } : { host, requested: true, outcome: "failed", healthy: false, detail: `registration was written but is unhealthy: ${problem.detail}` }; }; var tomlRegistration = (source) => { const table = /^\s*\[mcp_servers\.commitlore\]\s*$/m.exec(source); @@ -24417,7 +24576,7 @@ var tomlHost = async (path2, wrapper) => { } if (existing !== null) { const problem2 = await probeMcp(existing.command, existing.args); - if (problem2 !== null) return { host: "codex", requested: true, outcome: "failed", healthy: false, detail: `existing registration is unhealthy: ${problem2}` }; + if (isMcpProbeFailure(problem2)) return { host: "codex", requested: true, outcome: "failed", healthy: false, detail: `existing registration is unhealthy: ${problem2.detail}` }; return { host: "codex", requested: true, outcome: existing.command === wrapper ? "owned" : "custom-preserved", healthy: true, detail: existing.command === wrapper ? "healthy installer-owned registration" : "healthy custom registration preserved" }; } const escaped = JSON.stringify(wrapper); @@ -24427,7 +24586,7 @@ args = ["mcp"] `; try { mkdirSync11(dirname11(path2), { recursive: true }); - const temporary = join16(dirname11(path2), `.${String(path2.split("/").pop())}.commitlore-${process.pid}-${randomUUID()}.tmp`); + const temporary = join17(dirname11(path2), `.${String(path2.split("/").pop())}.commitlore-${process.pid}-${randomUUID()}.tmp`); try { writeFileSync17(temporary, next, { encoding: "utf8", mode: 384 }); if (process.env.COMMITLORE_INSTALLER_TEST_INTERRUPT_WRITE === "1") throw new Error("interrupted before atomic rename"); @@ -24443,18 +24602,18 @@ args = ["mcp"] return { host: "codex", requested: true, outcome: "failed", healthy: false, detail: `atomic config write failed: ${String(error2)}` }; } const problem = await probeMcp(wrapper, ["mcp"]); - return problem === null ? { host: "codex", requested: true, outcome: "installed", healthy: true, detail: "Codex config fallback added and live-verified" } : { host: "codex", requested: true, outcome: "failed", healthy: false, detail: `Codex registration was written but is unhealthy: ${problem}` }; + return !isMcpProbeFailure(problem) ? { host: "codex", requested: true, outcome: "installed", healthy: true, detail: "Codex config fallback added and live-verified" } : { host: "codex", requested: true, outcome: "failed", healthy: false, detail: `Codex registration was written but is unhealthy: ${problem.detail}` }; }; -var hasCommand = (command) => (process.env.PATH ?? "").split(delimiter).some((directory) => { - const path2 = join16(directory, command); +var hasCommand = (command) => (process.env.PATH ?? "").split(delimiter2).some((directory) => { + const path2 = join17(directory, command); try { - return !statSync9(path2).isDirectory(); + return !statSync10(path2).isDirectory(); } catch { return false; } }); var commandResult = (command, args) => { - const result = spawnSync6(command, args, { encoding: "utf8", shell: false }); + const result = spawnSync7(command, args, { encoding: "utf8", shell: false }); return { ok: result.status === 0 && result.error === void 0, stdout: result.stdout ?? "" }; }; var cliHost = async (host, wrapper) => { @@ -24466,13 +24625,13 @@ var cliHost = async (host, wrapper) => { return { host, requested: true, outcome: "failed", healthy: false, detail: "codex CLI returned an unverifiable registration" }; } const problem2 = await probeMcp(command, args); - if (problem2 !== null) return { host, requested: true, outcome: "failed", healthy: false, detail: `existing registration is unhealthy: ${problem2}` }; + if (isMcpProbeFailure(problem2)) return { host, requested: true, outcome: "failed", healthy: false, detail: `existing registration is unhealthy: ${problem2.detail}` }; return { host, requested: true, outcome: command === wrapper && args.length === 1 && args[0] === "mcp" ? "owned" : "custom-preserved", healthy: true, detail: command === wrapper && args.length === 1 && args[0] === "mcp" ? "healthy installer-owned registration" : "healthy custom registration preserved" }; } const added = commandResult("codex", ["mcp", "add", "commitlore", "--", wrapper, "mcp"]); if (!added.ok) return { host, requested: true, outcome: "failed", healthy: false, detail: "codex mcp add failed" }; const problem = await probeMcp(wrapper, ["mcp"]); - return problem === null ? { host, requested: true, outcome: "installed", healthy: true, detail: "Codex registration added and live-verified" } : { host, requested: true, outcome: "failed", healthy: false, detail: `Codex registration was written but is unhealthy: ${problem}` }; + return !isMcpProbeFailure(problem) ? { host, requested: true, outcome: "installed", healthy: true, detail: "Codex registration added and live-verified" } : { host, requested: true, outcome: "failed", healthy: false, detail: `Codex registration was written but is unhealthy: ${problem.detail}` }; }; var inspectAndApplyHosts = async (options) => { const requested = []; @@ -24480,21 +24639,21 @@ var inspectAndApplyHosts = async (options) => { const home = options.home; if (hasCommand("codex")) { requested.push(cliHost("codex", options.wrapper)); - } else if (existsSync21(join16(home, ".codex"))) { - requested.push(tomlHost(join16(home, ".codex", "config.toml"), options.wrapper)); + } else if (existsSync21(join17(home, ".codex"))) { + requested.push(tomlHost(join17(home, ".codex", "config.toml"), options.wrapper)); } else notDetected.push("codex"); const candidates = [ - ["gemini-cli", join16(home, ".gemini", "settings.json"), "json-mcpServers", hasCommand("gemini") || existsSync21(join16(home, ".gemini"))], - ["cursor", join16(home, ".cursor", "mcp.json"), "json-mcpServers", hasCommand("cursor") || existsSync21(join16(home, ".cursor"))], - ["windsurf", join16(home, ".codeium", "windsurf", "mcp_config.json"), "json-mcpServers", hasCommand("windsurf") || existsSync21(join16(home, ".codeium", "windsurf"))], - ["opencode", join16(home, ".config", "opencode", "opencode.json"), "json-mcp", hasCommand("opencode") || existsSync21(join16(home, ".config", "opencode"))] + ["gemini-cli", join17(home, ".gemini", "settings.json"), "json-mcpServers", hasCommand("gemini") || existsSync21(join17(home, ".gemini"))], + ["cursor", join17(home, ".cursor", "mcp.json"), "json-mcpServers", hasCommand("cursor") || existsSync21(join17(home, ".cursor"))], + ["windsurf", join17(home, ".codeium", "windsurf", "mcp_config.json"), "json-mcpServers", hasCommand("windsurf") || existsSync21(join17(home, ".codeium", "windsurf"))], + ["opencode", join17(home, ".config", "opencode", "opencode.json"), "json-mcp", hasCommand("opencode") || existsSync21(join17(home, ".config", "opencode"))] ]; for (const [host, path2, format, present2] of candidates) { if (present2) requested.push(jsonHost(host, path2, format, options.wrapper)); else notDetected.push(host); } - if (hasCommand("hermes") || existsSync21(join16(home, ".hermes"))) { - const result = spawnSync6(options.wrapper, ["hermes", "install", "--config", join16(home, ".hermes", "config.yaml"), "--command", options.wrapper, "--data-root", options.dataRoot, "--verify"], { stdio: "ignore", shell: false, timeout: 3e4 }); + if (hasCommand("hermes") || existsSync21(join17(home, ".hermes"))) { + const result = spawnSync7(options.wrapper, ["hermes", "install", "--config", join17(home, ".hermes", "config.yaml"), "--command", options.wrapper, "--data-root", options.dataRoot, "--verify"], { stdio: "ignore", shell: false, timeout: 3e4 }); requested.push(Promise.resolve(result.status === 0 ? { host: "hermes", requested: true, outcome: "installed", healthy: true, detail: "Hermes setup verified" } : { host: "hermes", requested: true, outcome: "failed", healthy: false, detail: "Hermes setup failed" })); } else notDetected.push("hermes"); const hosts = await Promise.all(requested); @@ -24511,7 +24670,7 @@ var register18 = (program3) => { // src/mcp/server.ts import { Console } from "node:console"; -import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve21, sep as sep5 } from "node:path"; +import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve21, sep as sep5 } from "node:path"; // node_modules/zod/v4/core/core.js var _a; @@ -33756,7 +33915,7 @@ var isCaptureTool = (name) => [PREPARE_CAPTURE_TOOL, VERIFY_CAPTURE_TOOL, STAGE_ var resolveRepoPath = (root, raw) => { if (raw === "" || raw === ".") return ""; if (raw.includes("\0")) throw new Error("path contains a NUL byte"); - if (isAbsolute3(raw)) { + if (isAbsolute4(raw)) { throw new Error(`path must be relative to the repository root: ${raw}`); } const resolved = resolve21(root, raw); @@ -34251,10 +34410,10 @@ var register21 = (program3) => { }; // src/core/codex-plugin.ts -import { spawnSync as spawnSync7 } from "node:child_process"; +import { spawnSync as spawnSync8 } from "node:child_process"; import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync25, rmSync as rmSync5, writeFileSync as writeFileSync18 } from "node:fs"; import { homedir as homedir2 } from "node:os"; -import { join as join17 } from "node:path"; +import { join as join18 } from "node:path"; // src/core/agent-configs.ts var AGENT_CONFIGS = [ @@ -34329,14 +34488,14 @@ var isCommitloreEntry = (format, entry, wrapperPath) => { // src/core/codex-plugin.ts var MARKER_VERSION = 1; -var defaultDataHome = () => process.platform === "win32" ? process.env["LOCALAPPDATA"] ?? join17(homedir2(), "AppData", "Local") : process.env["XDG_DATA_HOME"] ?? join17(homedir2(), ".local", "share"); +var defaultDataHome = () => process.platform === "win32" ? process.env["LOCALAPPDATA"] ?? join18(homedir2(), "AppData", "Local") : process.env["XDG_DATA_HOME"] ?? join18(homedir2(), ".local", "share"); var codexSaid = (result) => { const said = (result.stderr.trim() || result.stdout.trim()).split("\n")[0]?.trim() ?? ""; if (said === "") return []; return [`codex said: ${said}`]; }; var runCodexCommand = (args) => { - const result = spawnSync7("codex", args, { encoding: "utf8", timeout: 3e4 }); + const result = spawnSync8("codex", args, { encoding: "utf8", timeout: 3e4 }); return { status: result.status, stdout: result.stdout ?? "", @@ -34351,7 +34510,7 @@ var config2 = () => { }; var codexPluginSelector = (plugin = config2()) => `${plugin.plugin}@${plugin.marketplace}`; var codexPluginInstallCommand = () => "commitlore plugin install-codex"; -var codexPluginMarkerPath = (plugin = config2(), dataHome = defaultDataHome()) => join17(dataHome, ...plugin.dataRelativePath); +var codexPluginMarkerPath = (plugin = config2(), dataHome = defaultDataHome()) => join18(dataHome, ...plugin.dataRelativePath); var successful = (result) => result.status === 0 && result.error === void 0; var readMarketplaceState = (json, plugin) => { const namedInText = () => json.split("\n").some((line2) => line2.trim().startsWith(`${plugin.marketplace} `)) ? { kind: "unverifiable-present" } : { kind: "unverifiable-absent" }; @@ -34404,7 +34563,7 @@ var removeCodexPluginMarker = (plugin = config2(), dataHome = defaultDataHome()) }; var writeCodexPluginMarker = (plugin, dataHome) => { const markerPath = codexPluginMarkerPath(plugin, dataHome); - mkdirSync12(join17(markerPath, ".."), { recursive: true }); + mkdirSync12(join18(markerPath, ".."), { recursive: true }); writeFileSync18(markerPath, `${JSON.stringify(markerFor(plugin), null, 2)} `); }; @@ -35254,10 +35413,10 @@ var register25 = (program3) => { }; // src/commands/uninstall.ts -import { spawnSync as spawnSync8 } from "node:child_process"; +import { spawnSync as spawnSync9 } from "node:child_process"; import { existsSync as existsSync23, readFileSync as readFileSync28, rmSync as rmSync6, writeFileSync as writeFileSync20 } from "node:fs"; import { homedir as homedir3 } from "node:os"; -import { join as join18 } from "node:path"; +import { join as join19 } from "node:path"; var WRAPPER_MARKER = "# commitlore:wrapper:v1"; var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value); var withoutJsonEntry = (parsed, format, wrapper) => { @@ -35285,7 +35444,7 @@ var withoutTomlBlock = (contents, wrapper) => { return [...lines.slice(0, from), ...lines.slice(end)].join("\n"); }; var listCodexMcp = (command) => { - const listed = spawnSync8(command, ["mcp", "list", "--json"], { encoding: "utf8" }); + const listed = spawnSync9(command, ["mcp", "list", "--json"], { encoding: "utf8" }); if (listed.error?.code === "ENOENT") { return { state: "absent", servers: [] }; } @@ -35301,7 +35460,7 @@ var listCodexMcp = (command) => { var isInstalledCodexServer = (server, wrapper) => server.name === SERVER_KEY && server.transport?.type === "stdio" && server.transport.command === wrapper && Array.isArray(server.transport.args) && server.transport.args.length === 1 && server.transport.args[0] === "mcp"; var runUninstall = async (options = {}) => { const home = options.home ?? homedir3(); - const dataHome = options.dataHome ?? (process.platform === "win32" ? process.env["LOCALAPPDATA"] ?? join18(home, "AppData", "Local") : process.env["XDG_DATA_HOME"] ?? join18(home, ".local", "share")); + const dataHome = options.dataHome ?? (process.platform === "win32" ? process.env["LOCALAPPDATA"] ?? join19(home, "AppData", "Local") : process.env["XDG_DATA_HOME"] ?? join19(home, ".local", "share")); const dryRun = options.dryRun === true; const say = dryRun ? "would remove" : "removed"; const report = []; @@ -35309,7 +35468,7 @@ var runUninstall = async (options = {}) => { const kept = []; const failures = []; const runCodex = options.runCodex ?? runCodexCommand; - const wrapper = join18(home, ".local", "bin", "commitlore"); + const wrapper = join19(home, ".local", "bin", "commitlore"); if (existsSync23(wrapper)) { let contents; try { @@ -35369,7 +35528,7 @@ var runUninstall = async (options = {}) => { removeCodexPluginMarker(config3, dataHome); removed.push(`${selector} (Codex plugin)`); } - const dataRoot = join18(dataHome, "commitlore"); + const dataRoot = join19(dataHome, "commitlore"); if (existsSync23(dataRoot)) { if (retainDataRoot) { kept.push(dataRoot); @@ -35384,7 +35543,7 @@ var runUninstall = async (options = {}) => { const codexCommand = options.codexCommand ?? (options.home === void 0 ? "codex" : void 0); const codexList = codexCommand === void 0 ? null : listCodexMcp(codexCommand); if (codexConfig !== void 0 && codexList !== null) { - const path2 = join18(home, ...codexConfig.homeRelativePath); + const path2 = join19(home, ...codexConfig.homeRelativePath); if (codexList.state === "unavailable" || codexList.state === "invalid") { kept.push(path2); failures.push(path2); @@ -35399,7 +35558,7 @@ var runUninstall = async (options = {}) => { removed.push(`${path2} (${SERVER_KEY} entry)`); report.push(`${say}: the ${SERVER_KEY} entry through codex mcp remove`); } else { - const removedByCli = spawnSync8(codexCommand, ["mcp", "remove", SERVER_KEY], { encoding: "utf8" }); + const removedByCli = spawnSync9(codexCommand, ["mcp", "remove", SERVER_KEY], { encoding: "utf8" }); if (removedByCli.error === void 0 && removedByCli.status === 0) { removed.push(`${path2} (${SERVER_KEY} entry)`); report.push(`${say}: the ${SERVER_KEY} entry through codex mcp remove`); @@ -35415,7 +35574,7 @@ var runUninstall = async (options = {}) => { for (const config3 of AGENT_CONFIGS) { if (!isMcpAgentConfig(config3)) continue; if (config3.agent === "codex" && codexList !== null && codexList.state !== "absent") continue; - const path2 = join18(home, ...config3.homeRelativePath); + const path2 = join19(home, ...config3.homeRelativePath); if (!existsSync23(path2)) continue; let contents; try { @@ -35436,7 +35595,7 @@ var runUninstall = async (options = {}) => { } if (config3.format === "yaml-mcp_servers") { const next2 = removeHermesConfig(contents, { - wrapperPath: [wrapper, join18(dataRoot, "bin", "commitlore.cmd")], + wrapperPath: [wrapper, join19(dataRoot, "bin", "commitlore.cmd")], dataRoot, installedSkillsDir: installedPath("hermes", "skills") }); @@ -35487,6 +35646,26 @@ var registerUninstall = (program3) => { // src/cli.ts var pkg = { version: packageVersion() }; var STDIN_FD2 = 0; +var internalMcpProbe = async (command, rawArgs) => { + let args; + try { + const parsed = JSON.parse(rawArgs ?? "null"); + if (Array.isArray(parsed) && parsed.every((value) => typeof value === "string")) args = parsed; + } catch { + } + const result = command === void 0 || args === void 0 ? { kind: "failure", reason: "probe-unavailable", detail: "could not read MCP verification arguments" } : await probeMcp(command, args); + await new Promise((resolve22) => process.stdout.write(JSON.stringify(result), () => resolve22())); +}; +var internalArguments = process.argv.slice(2); +if (internalArguments[0] === "internal" && internalArguments[1] === "mcp-probe") { + const commandIndex = internalArguments.indexOf("--command"); + const argsIndex = internalArguments.indexOf("--args-json"); + await internalMcpProbe( + commandIndex === -1 ? void 0 : internalArguments[commandIndex + 1], + argsIndex === -1 ? void 0 : internalArguments[argsIndex + 1] + ); + process.exit(0); +} var readMessage = (messageFile) => { if (messageFile !== void 0) return readFileSync29(messageFile, "utf8"); if (process.stdin.isTTY) { @@ -35556,6 +35735,9 @@ program2.command("parse").description("Parse a commit message into its CommitLor ).action((options) => { runParse(options); }); +program2.command("internal", { hidden: true }).command("mcp-probe", { hidden: true }).requiredOption("--command ", "MCP command to verify").requiredOption("--args-json ", "JSON array of MCP command arguments").action(async (options) => { + await internalMcpProbe(options.command, options.argsJson); +}); register24(program2); register7(program2); register25(program2); diff --git a/dist/core/mcp-probe.d.ts b/dist/core/mcp-probe.d.ts new file mode 100644 index 00000000..8a1b8149 --- /dev/null +++ b/dist/core/mcp-probe.d.ts @@ -0,0 +1,34 @@ +/** + * A small, shared CommitLore MCP identity probe. + * + * A registration proves only that a host has a command to try. This probe + * establishes the stronger fact that the command answers as CommitLore: it + * completes initialize with a name and version, then reports whether the + * advertised tools form the read-delivery or capture-initiation surface. + */ +export type McpProbeFailureKind = 'command-not-found' | 'command-is-directory' | 'command-not-executable' | 'command-could-not-start' | 'command-exited' | 'command-closed-input' | 'initialize-timed-out' | 'foreign-server' | 'missing-tools' | 'probe-unavailable'; +export interface McpProbeFailure { + kind: 'failure'; + reason: McpProbeFailureKind; + detail: string; + cleanup?: McpProbeCleanup; +} +export interface McpProbeSuccess { + /** The server identifies as CommitLore; kind records its advertised tool set. */ + kind: 'read-delivery' | 'capture-initiator'; + detail: string; + cleanup: McpProbeCleanup; +} +export type McpProbeResult = McpProbeFailure | McpProbeSuccess; +export type McpProbeCleanup = 'not-needed' | 'reclaimed' | 'could-not-reclaim'; +export declare const MCP_READ_TOOLS: readonly ["commitlore_query", "commitlore_before_change"]; +export declare const MCP_CAPTURE_TOOLS: readonly ["commitlore_prepare_capture", "commitlore_verify_capture", "commitlore_stage_capture"]; +export declare const isMcpProbeFailure: (result: McpProbeResult) => result is McpProbeFailure; +/** Speak enough MCP to distinguish a launchable command from usable CommitLore. */ +export declare const probeMcp: (command: string, args: string[]) => Promise; +/** + * Doctor's report API is synchronous. Run this same async probe through the + * installation's declared runtime bundle rather than maintaining a second, + * subtly different protocol entry point. + */ +export declare const probeMcpSync: (command: string, args: string[]) => McpProbeResult; diff --git a/dist/core/mcp-probe.js b/dist/core/mcp-probe.js new file mode 100644 index 00000000..0c05210c --- /dev/null +++ b/dist/core/mcp-probe.js @@ -0,0 +1,267 @@ +/** + * A small, shared CommitLore MCP identity probe. + * + * A registration proves only that a host has a command to try. This probe + * establishes the stronger fact that the command answers as CommitLore: it + * completes initialize with a name and version, then reports whether the + * advertised tools form the read-delivery or capture-initiation surface. + */ +import { accessSync, constants, statSync } from 'node:fs'; +import { spawn, spawnSync } from 'node:child_process'; +import { delimiter, isAbsolute, join } from 'node:path'; +import { installedPath } from './paths.js'; +const failure = (reason, detail, cleanup) => ({ kind: 'failure', reason, detail, ...(cleanup === undefined ? {} : { cleanup }) }); +export const MCP_READ_TOOLS = ['commitlore_query', 'commitlore_before_change']; +export const MCP_CAPTURE_TOOLS = [ + 'commitlore_prepare_capture', + 'commitlore_verify_capture', + 'commitlore_stage_capture', +]; +export const isMcpProbeFailure = (result) => result.kind === 'failure'; +const CLEANUP_GRACE_MS = 250; +/** + * How long a registered server gets to answer `initialize` (#640). + * + * The old value was five seconds and it was too tight to be a verdict. On a + * Windows runner a *passing* probe consumed 4478ms of it, because the chain is + * doctor -> this sidecar's node -> cmd.exe -> the server's own node: three + * process starts before a byte moves. The same registration on a slower + * machine reported `initialize-timed-out`, and doctor called it unhealthy. + * + * Fifteen seconds is not a guess at how slow a machine can be; it is a budget + * chosen so the measured worst *passing* case has room to be three times + * slower before a working registration is called broken. A genuinely dead + * command still fails long before it, through `command-exited` or + * `command-could-not-start`, which do not wait for this at all. + */ +const DEFAULT_INITIALIZE_TIMEOUT_MS = 15_000; +/** + * Tests that assert the timeout path would otherwise wait the full budget + * twice, and a fixture that never answers is exactly what they need. The + * sidecar inherits the environment, so an override reaches the probe wherever + * it actually runs. + */ +const initializeTimeoutMs = () => { + const raw = Number(process.env['COMMITLORE_MCP_PROBE_TIMEOUT_MS']); + return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INITIALIZE_TIMEOUT_MS; +}; +const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); +/** + * The synchronous wrapper cannot return until its helper exits, so a completed + * protocol exchange must also stop the server that supplied it. On POSIX the + * server receives its own process group: killing that group also handles a + * launcher which did not replace itself with the actual server process. + */ +const stopProbeChild = async (child) => { + if (child.exitCode !== null || child.signalCode !== null) + return 'not-needed'; + let exitedResolve; + const exited = new Promise((resolve) => { + exitedResolve = resolve; + }); + child.once('exit', () => exitedResolve?.()); + if (process.platform === 'win32') { + if (child.pid === undefined) + return 'could-not-reclaim'; + const result = spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], { + stdio: 'ignore', + windowsHide: true, + }); + if (result.error !== undefined || result.status !== 0) + return 'could-not-reclaim'; + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + return 'reclaimed'; + } + const signal = (value) => { + if (child.pid !== undefined) { + try { + process.kill(-child.pid, value); + return; + } + catch { + // A very short-lived child can leave its process group before this + // signal. Its direct process handle is still the portable fallback. + } + } + child.kill(value); + }; + signal('SIGTERM'); + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + if (child.exitCode === null && child.signalCode === null) { + signal('SIGKILL'); + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + } + return child.exitCode === null && child.signalCode === null ? 'could-not-reclaim' : 'reclaimed'; +}; +const commandPath = (command) => { + const bare = !isAbsolute(command) && !command.includes('/'); + const pathCandidates = bare + ? (process.env['PATH'] ?? '').split(delimiter).filter(Boolean).map((directory) => join(directory, command)) + : [command]; + const hasExtension = command.lastIndexOf('.') > command.lastIndexOf('/'); + const extensions = process.platform === 'win32' && bare && !hasExtension + ? (process.env['PATHEXT'] ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) + : []; + const candidates = pathCandidates.flatMap((candidate) => [ + candidate, + ...extensions.map((extension) => candidate + extension), + ]); + let nonExecutable; + for (const candidate of candidates) { + try { + if (statSync(candidate).isDirectory()) + return failure('command-is-directory', 'command is a directory'); + accessSync(candidate, constants.X_OK); + return candidate; + } + catch (error) { + if (error.code !== 'ENOENT') { + nonExecutable ??= failure('command-not-executable', 'command is not executable'); + } + } + } + return nonExecutable ?? failure('command-not-found', 'command does not exist'); +}; +const needsWindowsCommandShell = (command) => process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(command); +/** Speak enough MCP to distinguish a launchable command from usable CommitLore. */ +export const probeMcp = async (command, args) => { + const resolved = commandPath(command); + if (typeof resolved !== 'string') + return resolved; + return new Promise((resolve) => { + let settled = false; + let child; + let timer; + const finish = (problem) => { + if (settled) + return; + settled = true; + if (timer !== undefined) + clearTimeout(timer); + void (async () => { + const cleanup = child === undefined ? 'not-needed' : await stopProbeChild(child); + if (cleanup === 'could-not-reclaim') { + resolve(failure('probe-unavailable', 'MCP verification completed but could not reclaim its child process tree', cleanup)); + return; + } + resolve({ ...problem, cleanup }); + })(); + }; + try { + const childEnv = { ...process.env }; + delete childEnv['COMMITLORE_MCP_PROBE']; + child = spawn(resolved, args, { + stdio: ['pipe', 'pipe', 'pipe'], + shell: needsWindowsCommandShell(resolved), + env: childEnv, + detached: process.platform !== 'win32', + }); + } + catch (error) { + finish(failure('command-could-not-start', `could not start command: ${error instanceof Error ? error.message : String(error)}`)); + return; + } + let buffer = ''; + let initialized = false; + const timeoutMs = initializeTimeoutMs(); + timer = setTimeout(() => finish(failure('initialize-timed-out', `MCP initialize timed out after ${timeoutMs}ms`)), timeoutMs); + child.once('error', (error) => finish(failure('command-could-not-start', `could not start command: ${error.message}`))); + child.once('exit', (code) => { + if (!settled) + finish(failure('command-exited', `command exited before MCP verification (status ${String(code)})`)); + }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (chunk) => { + buffer += chunk; + const lines = buffer.split('\n'); + buffer = lines.pop() ?? ''; + for (const line of lines) { + let message; + try { + message = JSON.parse(line); + } + catch { + continue; + } + if (!initialized && message.id === 1) { + const info = message.result?.serverInfo; + if (info?.name !== 'commitlore' || typeof info.version !== 'string' || info.version === '') { + finish(failure('foreign-server', 'MCP initialize did not identify CommitLore with a version')); + return; + } + initialized = true; + child?.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })}\n`); + } + else if (initialized && message.id === 2) { + const tools = new Set((message.result?.tools ?? []).map((tool) => tool.name)); + if (!MCP_READ_TOOLS.every((tool) => tools.has(tool))) { + finish(failure('missing-tools', 'MCP server lacks CommitLore read-delivery tools')); + } + else if (MCP_CAPTURE_TOOLS.every((tool) => tools.has(tool))) { + finish({ kind: 'capture-initiator', detail: 'MCP server advertises CommitLore read and capture tools', cleanup: 'not-needed' }); + } + else { + finish({ kind: 'read-delivery', detail: 'MCP server advertises CommitLore read tools but not the complete capture tool set', cleanup: 'not-needed' }); + } + return; + } + } + }); + // A command that is not an MCP server can close before this write. Keep + // the stream error handled: EPIPE is an unhealthy registration, not an + // inspector crash (the deterministic close-stdin case covers this race). + child.stdin.on('error', () => finish(failure('command-closed-input', 'command closed its input before MCP verification'))); + // Let a launcher complete its own descriptor setup before the first + // protocol write. This preserves the probe's distinct closed-input result + // when the wrapper is the full CLI bundle rather than the former sidecar. + setTimeout(() => { + child?.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'commitlore-probe', version: '1' } } })}\n`); + }, 200); + }); +}; +/** + * Doctor's report API is synchronous. Run this same async probe through the + * installation's declared runtime bundle rather than maintaining a second, + * subtly different protocol entry point. + */ +export const probeMcpSync = (command, args) => { + const result = spawnSync(process.execPath, [ + installedPath('dist', 'commitlore.mjs'), + 'internal', + 'mcp-probe', + '--command', + command, + '--args-json', + JSON.stringify(args), + ], { + encoding: 'utf8', + // The helper owns the protocol timeout and needs room beyond it to reap a + // stubborn server. This outer bound is only a safety net for a broken + // helper, not the server-response timeout we report — so it is derived + // from that budget rather than written next to it, because a constant that + // silently fell below it would kill the helper before it could answer and + // report the death as the server's fault. + timeout: initializeTimeoutMs() + 2_000, + env: { ...process.env, COMMITLORE_MCP_PROBE: '1' }, + }); + if (result.error !== undefined) { + return failure('probe-unavailable', `could not run MCP verification: ${result.error.message}`); + } + try { + const parsed = JSON.parse(result.stdout); + if (typeof parsed === 'object' + && parsed !== null + && typeof parsed.kind === 'string' + && typeof parsed.detail === 'string' + && (parsed.kind === 'read-delivery' + || parsed.kind === 'capture-initiator' + || (parsed.kind === 'failure' && typeof parsed.reason === 'string'))) { + return parsed; + } + } + catch { + // The diagnostic below includes the child status without trusting output. + } + return failure('probe-unavailable', `could not read MCP verification result (status ${String(result.status)})`); +}; +//# sourceMappingURL=mcp-probe.js.map \ No newline at end of file diff --git a/dist/core/mcp-probe.js.map b/dist/core/mcp-probe.js.map new file mode 100644 index 00000000..eb0c68ab --- /dev/null +++ b/dist/core/mcp-probe.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mcp-probe.js","sourceRoot":"","sources":["../../src/core/mcp-probe.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/core/mcp-registration.d.ts b/dist/core/mcp-registration.d.ts index 75307f75..ecb0e48c 100644 --- a/dist/core/mcp-registration.d.ts +++ b/dist/core/mcp-registration.d.ts @@ -26,6 +26,11 @@ export declare const MCP_SERVER_ARGS: readonly ["mcp"]; * second: `{"command": "false"}` read as a working capture server. */ export declare const registeredMcpCommand: (cwd: string) => string | null; +/** The complete launch command a host will use, when its argv is parseable. */ +export declare const registeredMcpLaunch: (cwd: string) => { + command: string; + args: string[]; +} | null; /** * Whether the registered command is the one `init` writes. * diff --git a/dist/core/mcp-registration.js b/dist/core/mcp-registration.js index 73cfdb67..2fb238ab 100644 --- a/dist/core/mcp-registration.js +++ b/dist/core/mcp-registration.js @@ -50,6 +50,11 @@ const isLaunchableEntry = (value) => isJsonObject(value) && typeof value['comman * second: `{"command": "false"}` read as a working capture server. */ export const registeredMcpCommand = (cwd) => { + const launch = registeredMcpLaunch(cwd); + return launch?.command ?? null; +}; +/** The complete launch command a host will use, when its argv is parseable. */ +export const registeredMcpLaunch = (cwd) => { const path = mcpRegistrationPath(cwd); if (path === null) return null; @@ -68,7 +73,10 @@ export const registeredMcpCommand = (cwd) => { const entry = servers[MCP_SERVER_KEY]; if (!isLaunchableEntry(entry)) return null; - return String(entry['command']); + const args = entry['args']; + if (args !== undefined && (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string'))) + return null; + return { command: String(entry['command']), args: (args ?? []) }; }; /** * Whether the registered command is the one `init` writes. diff --git a/dist/core/mcp-registration.js.map b/dist/core/mcp-registration.js.map index d63ddfa8..836a1f44 100644 --- a/dist/core/mcp-registration.js.map +++ b/dist/core/mcp-registration.js.map @@ -1 +1 @@ -{"version":3,"file":"mcp-registration.js","sourceRoot":"","sources":["../../src/core/mcp-registration.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mcp-registration.js","sourceRoot":"","sources":["../../src/core/mcp-registration.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/installer/canonical-artifact.json b/installer/canonical-artifact.json index a58b624a..d49155c7 100644 --- a/installer/canonical-artifact.json +++ b/installer/canonical-artifact.json @@ -15,10 +15,10 @@ "tsconfig.json", "src" ], - "sha256": "afa98028ec4993fbc08c1c82786217af028a1b04b38ba78b60b29c689dbebabe" + "sha256": "fd058ff6cc0198ec9063443972199e1ef7de5cf3bba50ece7ead6a3fb60f892b" }, "artifact": { - "sha256": "9a6217b0f57f566d1eeff74e3238204e4c037b6f210e7dae68eb8c38ce07bcd9", + "sha256": "90b51c8513ffc97d127c711228310fda22e37669152196856397fd407d009990", "files": [ { "path": "dist/cli.d.ts", @@ -26,11 +26,11 @@ }, { "path": "dist/cli.js", - "sha256": "423e394b295326cee604419adcb7c83d7685ee966f80273b71aba980bbe253fc" + "sha256": "0d44931c331fdf39859db71aeb16e367266db887266c4a9e288873781ad594dd" }, { "path": "dist/cli.js.map", - "sha256": "cd50af620163af8ef14e2ad8e5ddb8fa44b0299f9cd5d504855ffc4ce2c1b3c2" + "sha256": "290a365c8f44aa961e1797f617f619c959281f7394d0a1c88ea310db0a933647" }, { "path": "dist/commands/auto.d.ts", @@ -134,11 +134,11 @@ }, { "path": "dist/commands/doctor/checks/capture-unattended-initiator.js", - "sha256": "07aafa7f42f3cfaa535177a4d451105d30755062e907c7f53b16c14f8ee1b6c0" + "sha256": "e9e8c5783b5f4f499a4b2a788e81773c2e6ab574cc088e9dd5003599b42605f5" }, { "path": "dist/commands/doctor/checks/capture-unattended-initiator.js.map", - "sha256": "6783c3b27cb5ecd2c8a891ef8be7c3b7be27c2413b88e4e93a46e42a4db58582" + "sha256": "4d1334fd9e2f35f4924668578b3c60ee0f3c1cf53885091f86ae176ba8f0dd6a" }, { "path": "dist/commands/doctor/checks/delivery-directive-trust-mode.d.ts", @@ -454,15 +454,15 @@ }, { "path": "dist/commands/installer-hosts.d.ts", - "sha256": "9a0cd789d71aaf614bfe43489bbe26fd3a94de52a423c57964aad473a4a9decc" + "sha256": "b6461546d217aa9fef0d1aea8e10561e560396676da7a2a54a2156af3acd5dc8" }, { "path": "dist/commands/installer-hosts.js", - "sha256": "aed67cb5fe1093df3e6ee0a745bf27b1fa901bb25a8bc7a34d1e3a0da52c5d13" + "sha256": "86ff3416516d96127b0eec884d8b0213fdd27f2bde38a688c8533d55eafa69bc" }, { "path": "dist/commands/installer-hosts.js.map", - "sha256": "3e2d4f7712e25def2499e36c761093cf71b58c992ea4ebeadb8c885cd3d5f9df" + "sha256": "a04d4cde2809f2171bcd7613b30311d9070488529b85957eb9398d1d3c24bede" }, { "path": "dist/commands/mcp.d.ts", @@ -574,7 +574,7 @@ }, { "path": "dist/commitlore.mjs", - "sha256": "74e5e530404eb139511dadc7eb80cc4e0ab1f08ae543debb337a3dd0dcebddb3" + "sha256": "c226d187a8717b7e47a97b1f6f299534bb571d824b06b7c12e7bf6bbee64fb38" }, { "path": "dist/core/agent-configs.d.ts", @@ -816,17 +816,29 @@ "path": "dist/core/inject.js.map", "sha256": "b195e0942ce3a342093db21cd45f56cd7ec14263bdaf1e1a4b7b538dad762a33" }, + { + "path": "dist/core/mcp-probe.d.ts", + "sha256": "261ff43acd9c48ad58937851f30a344206bad8f560bfde2ee33fdddb01c987d9" + }, + { + "path": "dist/core/mcp-probe.js", + "sha256": "ca3e1e7e0b267b963a442b565c3709e2915445b80931b57b79c3f19553608e08" + }, + { + "path": "dist/core/mcp-probe.js.map", + "sha256": "37db93a21e516a85f75ca5f66006e97d8160b0c94a2f4c8b7699768eccb662dd" + }, { "path": "dist/core/mcp-registration.d.ts", - "sha256": "fad8293a25b0c121751b6972f840ac45e7c5e24149928c9b6d48bbed583922c7" + "sha256": "0f4100ae9ab0b6762b1cdd29d10c6333a85606d93598521465a778cf3b0d55fd" }, { "path": "dist/core/mcp-registration.js", - "sha256": "08aec31bf1a7b97fbf0f363643b9eb1cbcc749007ef30bc699d1f2b0f707cbc3" + "sha256": "5f271b7e159691b26f0b0fe13298ccd19ee643c14be872e0fad1dc4725dfc139" }, { "path": "dist/core/mcp-registration.js.map", - "sha256": "4c58a270d3589dae9636553ceeaf40a0519343acbf32ad160e3d9dc7ed15a033" + "sha256": "81626fd1c88c788e5fe6d716cc429af3bd1fe9eed83d6b0b1a1942436bb6b351" }, { "path": "dist/core/notes.d.ts", diff --git a/scripts/check-exact-head-ci.mjs b/scripts/check-exact-head-ci.mjs index 3bdf89eb..19a26728 100644 --- a/scripts/check-exact-head-ci.mjs +++ b/scripts/check-exact-head-ci.mjs @@ -40,7 +40,7 @@ const CI_WORKFLOW_FILE_PATH = fileURLToPath(new URL(`../${CI_WORKFLOW_PATH}`, im // shell command; without this lock replacing every job body with `true` would // still look like a real successful run. Update deliberately with the CI // workflow when its reviewed job contract changes. -export const EXPECTED_CI_WORKFLOW_SHA256 = 'f41f1b16b34f73a999fd8909e6e31e709f26a76d911adc8b2ea4a19a131c8e29'; +export const EXPECTED_CI_WORKFLOW_SHA256 = '87f33a8cea5b04369e118f68d64798f46dcf7dd7d549af374c5ee29b89c9352e'; // Fixed rather than inferred from returned jobs: absence must fail rather // than define itself away. `lint` only runs for pull requests and is therefore diff --git a/src/cli.ts b/src/cli.ts index b956e5b2..73959282 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -19,6 +19,7 @@ import { register as registerAuto } from './commands/auto.js'; import { register as registerCapture } from './commands/capture.js'; import { register as registerDemo } from './commands/demo.js'; import { packageVersion } from './core/paths.js'; +import { probeMcp, type McpProbeResult } from './core/mcp-probe.js'; import { formatRuntimeIdentity, runtimeIdentity } from './core/runtime-identity.js'; import { register as registerDoctor } from './commands/doctor.js'; import { register as registerHarvest } from './commands/harvest.js'; @@ -48,6 +49,31 @@ const pkg: { version?: string } = { version: packageVersion() }; const STDIN_FD = 0; +const internalMcpProbe = async (command: string | undefined, rawArgs: string | undefined): Promise => { + let args: string[] | undefined; + try { + const parsed = JSON.parse(rawArgs ?? 'null'); + if (Array.isArray(parsed) && parsed.every((value) => typeof value === 'string')) args = parsed; + } catch { + // The result below is a probe response, not an uncaught helper error. + } + const result: McpProbeResult = command === undefined || args === undefined + ? { kind: 'failure', reason: 'probe-unavailable', detail: 'could not read MCP verification arguments' } + : await probeMcp(command, args); + await new Promise((resolve) => process.stdout.write(JSON.stringify(result), () => resolve())); +}; + +const internalArguments = process.argv.slice(2); +if (internalArguments[0] === 'internal' && internalArguments[1] === 'mcp-probe') { + const commandIndex = internalArguments.indexOf('--command'); + const argsIndex = internalArguments.indexOf('--args-json'); + await internalMcpProbe( + commandIndex === -1 ? undefined : internalArguments[commandIndex + 1], + argsIndex === -1 ? undefined : internalArguments[argsIndex + 1], + ); + process.exit(0); +} + const readMessage = (messageFile: string | undefined): string => { if (messageFile !== undefined) return readFileSync(messageFile, 'utf8'); if (process.stdin.isTTY) { @@ -159,6 +185,20 @@ program runParse(options); }); +/** + * An implementation-only bridge for synchronous doctor checks. Both command + * levels are hidden so the bundle keeps one public command surface while the + * probe can still run the same bundled code in a child process. + */ +program + .command('internal', { hidden: true }) + .command('mcp-probe', { hidden: true }) + .requiredOption('--command ', 'MCP command to verify') + .requiredOption('--args-json ', 'JSON array of MCP command arguments') + .action(async (options: { command: string; argsJson: string }) => { + await internalMcpProbe(options.command, options.argsJson); + }); + registerSync(program); registerPrePush(program); registerValidate(program); diff --git a/src/commands/doctor/checks/capture-unattended-initiator.ts b/src/commands/doctor/checks/capture-unattended-initiator.ts index 489b706a..13763d17 100644 --- a/src/commands/doctor/checks/capture-unattended-initiator.ts +++ b/src/commands/doctor/checks/capture-unattended-initiator.ts @@ -10,9 +10,11 @@ import { POLICY_FILE_NAME, resolvePolicy } from '../../../core/capture-policy.js import { MCP_REGISTRATION_FILE, registeredMcpCommand, + registeredMcpLaunch, registersCommitloreMcpServer, registrationIsOurs, } from '../../../core/mcp-registration.js'; +import { isMcpProbeFailure, probeMcpSync } from '../../../core/mcp-probe.js'; import { check, type Category, type DoctorCheck, type DoctorContext } from '../model.js'; /** @@ -86,30 +88,62 @@ export const checkUnattendedCaptureInitiator = (ctx: DoctorContext): DoctorCheck ); } - if (registersCommitloreMcpServer(cwd)) { - // Registered and *ours* is the only combination this can vouch for. - // Anything else is an entry an operator chose, which is theirs to keep and - // not this report's to call verified: `{"command": "false"}` was reading as - // a working capture server. + const launch = registeredMcpLaunch(cwd); + if (launch !== null && registersCommitloreMcpServer(cwd)) { const command = registeredMcpCommand(cwd); const ours = registrationIsOurs(cwd); - if (!ours) { + const probe = probeMcpSync(launch.command, launch.args); + if (isMcpProbeFailure(probe)) { + // A timeout is an observation about this attempt, not a verdict about + // the registration (#640). Measured on a Windows runner: a *passing* + // probe consumed 4478ms of its 5000ms budget, because the chain is + // doctor -> sidecar node -> cmd.exe -> the server's own node, three + // process starts before a byte is exchanged. On a slower machine the + // same healthy registration reports the same silence — and saying "it is + // unhealthy, repair it" sends an operator to fix something that works. + const unverified = probe.reason === 'initialize-timed-out'; return check( id, category, title, 'warn', - `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, which is not the command ` + - 'this tool writes — it is left alone, and whether it starts a capture server is unverified', - `check that ${JSON.stringify(command)} is a CommitLore MCP server, or remove the entry and run commitlore init`, + unverified + ? `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, and it did not answer in time to be verified: ${probe.detail}. This does not say the registration is broken — a cold start on a loaded machine can outlast the probe.` + : `${MCP_REGISTRATION_FILE} registers ${JSON.stringify(command)} under commitlore, but it is unhealthy: ${probe.detail}`, + unverified + ? `rerun commitlore doctor when the machine is quieter; if it keeps timing out, start ${JSON.stringify(command)} by hand and check that it answers an MCP initialize` + : `repair ${JSON.stringify(command)} so it answers as a CommitLore MCP server, or remove the entry and run commitlore init`, false, undefined, { evidence: { policy: 'unattended', ordinary_git_commit: 'cannot-initiate', - initiator: 'registered-command-unverified', + initiator: unverified ? 'registered-command-unverified' : 'registered-command-unhealthy', command: command ?? '', + probe: probe.reason, + }, + }, + ); + } + if (probe.kind === 'read-delivery') { + return check( + id, + category, + title, + 'warn', + `${MCP_REGISTRATION_FILE} registers a live CommitLore read-delivery server${ours ? '' : ' through a custom wrapper'}, but it does not advertise all three capture tools, so it is not an unattended capture initiator`, + 'register a CommitLore MCP server that advertises commitlore_prepare_capture, commitlore_verify_capture, and commitlore_stage_capture', + false, + undefined, + { + evidence: { + policy: 'unattended', + ordinary_git_commit: 'cannot-initiate', + initiator: 'read-delivery-only', + registration: ours ? 'installer-owned' : 'custom-preserved', + verified: 'identity-and-read-tools', + capture_tools: 'not-complete', }, }, ); @@ -119,8 +153,8 @@ export const checkUnattendedCaptureInitiator = (ctx: DoctorContext): DoctorCheck category, title, 'ok', - `${MCP_REGISTRATION_FILE} registers the capture server, so a host loading it can start unattended capture; ` + - 'an ordinary git commit outside that host still cannot', + `${MCP_REGISTRATION_FILE} registers a live CommitLore MCP server${ours ? '' : ' through a custom wrapper'} that advertises the required read and capture tools; ` + + 'this verifies identity and tool set, not asset readiness — required assets such as SPEC.md can still be missing and make prepare fail; an ordinary git commit outside that host still cannot initiate capture', null, false, undefined, @@ -128,10 +162,10 @@ export const checkUnattendedCaptureInitiator = (ctx: DoctorContext): DoctorCheck evidence: { policy: 'unattended', ordinary_git_commit: 'cannot-initiate', - initiator: 'mcp-server-registered', - // Registration is configuration, not observation: nothing here - // proves a host has ever called the tool. - verified: 'registration-only', + initiator: 'capture-tools-advertised', + registration: ours ? 'installer-owned' : 'custom-preserved', + verified: 'identity-and-read-and-capture-tools', + asset_readiness: 'not-verified', }, }, ); diff --git a/src/commands/installer-hosts.ts b/src/commands/installer-hosts.ts index 034100c5..bcb90357 100644 --- a/src/commands/installer-hosts.ts +++ b/src/commands/installer-hosts.ts @@ -7,13 +7,14 @@ * process is what makes an installer success claim useful on both platforms. */ -import { accessSync, constants, existsSync, mkdirSync, renameSync, statSync, unlinkSync, writeFileSync, readFileSync } from 'node:fs'; +import { existsSync, mkdirSync, renameSync, statSync, unlinkSync, writeFileSync, readFileSync } from 'node:fs'; import { delimiter, dirname, join } from 'node:path'; import { randomUUID } from 'node:crypto'; -import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from 'node:child_process'; +import { spawnSync } from 'node:child_process'; import type { Command } from 'commander'; +import { isMcpProbeFailure, probeMcp } from '../core/mcp-probe.js'; import { runtimeIdentity, type RuntimeIdentity } from '../core/runtime-identity.js'; export const INSTALLER_HOSTS_SCHEMA = 'commitlore_installer_hosts.v1'; @@ -88,79 +89,6 @@ const atomicJsonWrite = (path: string, value: unknown): void => { } }; -const executable = (command: string): string | null => { - try { - if (statSync(command).isDirectory()) return 'command is a directory'; - accessSync(command, constants.X_OK); - return null; - } catch { - return 'command does not exist or is not executable'; - } -}; - -/** Speak enough MCP to distinguish an executable from a usable CommitLore server. */ -export const probeMcp = async (command: string, args: string[]): Promise => { - const unusable = executable(command); - if (unusable !== null) return unusable; - return new Promise((resolve) => { - let settled = false; - const finish = (problem: string | null): void => { - if (settled) return; - settled = true; - child?.kill(); - resolve(problem); - }; - let child: ChildProcessWithoutNullStreams | undefined; - try { - child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], shell: false }); - } catch (error) { - finish(`could not start command: ${error instanceof Error ? error.message : String(error)}`); - return; - } - let buffer = ''; - let initialized = false; - const timer = setTimeout(() => finish('MCP initialize timed out'), 5_000); - child.once('error', (error) => finish(`could not start command: ${error.message}`)); - child.once('exit', (code) => { - if (!settled) finish(`command exited before MCP verification (status ${String(code)})`); - }); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (chunk: string) => { - buffer += chunk; - const lines = buffer.split('\n'); - buffer = lines.pop() ?? ''; - for (const line of lines) { - let message: { id?: number; result?: { serverInfo?: { name?: unknown; version?: unknown }; tools?: Array<{ name?: unknown }> } }; - try { message = JSON.parse(line) as typeof message; } catch { continue; } - if (!initialized && message.id === 1) { - const info = message.result?.serverInfo; - if (info?.name !== 'commitlore' || typeof info.version !== 'string' || info.version === '') { - clearTimeout(timer); - finish('MCP initialize did not identify CommitLore with a version'); - return; - } - initialized = true; - child?.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })}\n`); - } else if (initialized && message.id === 2) { - const tools = new Set((message.result?.tools ?? []).map((tool) => tool.name)); - clearTimeout(timer); - finish(tools.has('commitlore_query') && tools.has('commitlore_before_change') ? null : 'MCP server lacks CommitLore minimum tools'); - return; - } - } - }); - // A command that is not an MCP server usually proves it by exiting at once, - // and then this write lands on a closed pipe. Node delivers EPIPE as an - // `error` event on the stream, and an unhandled one takes the whole - // inspector down with it: the installer then exits non-zero having said - // nothing about any host — a worse answer than the `unhealthy` it was one - // step away from giving. Whether the write or the child's exit wins is a - // race, which is why this survived locally and died under CI load. - child.stdin.on('error', () => finish('command closed its input before MCP verification')); - child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'commitlore-installer', version: '1' } } })}\n`); - }); -}; - const jsonHost = async (host: string, path: string, format: JsonFormat, wrapper: string): Promise => { let config: JsonObject; let existed = true; @@ -185,7 +113,7 @@ const jsonHost = async (host: string, path: string, format: JsonFormat, wrapper: const launch = commandOf(format, entry); if (launch === null) return { host, requested: true, outcome: 'failed', healthy: false, detail: 'commitlore registration has no runnable command and args' }; const problem = await probeMcp(launch.command, launch.args); - if (problem !== null) return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem}` }; + if (isMcpProbeFailure(problem)) return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem.detail}` }; return { host, requested: true, outcome: ownEntry(format, entry, wrapper) ? 'owned' : 'custom-preserved', healthy: true, detail: ownEntry(format, entry, wrapper) ? 'healthy installer-owned registration' : 'healthy custom registration preserved' }; } config[key] = { ...group, commitlore: entryFor(format, wrapper) }; @@ -193,9 +121,9 @@ const jsonHost = async (host: string, path: string, format: JsonFormat, wrapper: return { host, requested: true, outcome: 'failed', healthy: false, detail: `atomic config write failed: ${error instanceof Error ? error.message : String(error)}` }; } const problem = await probeMcp(wrapper, ['mcp']); - return problem === null + return !isMcpProbeFailure(problem) ? { host, requested: true, outcome: 'installed', healthy: true, detail: existed ? 'registration added and live-verified' : 'registration created and live-verified' } - : { host, requested: true, outcome: 'failed', healthy: false, detail: `registration was written but is unhealthy: ${problem}` }; + : { host, requested: true, outcome: 'failed', healthy: false, detail: `registration was written but is unhealthy: ${problem.detail}` }; }; const tomlRegistration = (source: string): { command: string; args: string[] } | null => { @@ -219,7 +147,7 @@ const tomlHost = async (path: string, wrapper: string): Promise => { } if (existing !== null) { const problem = await probeMcp(existing.command, existing.args); - if (problem !== null) return { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem}` }; + if (isMcpProbeFailure(problem)) return { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem.detail}` }; return { host: 'codex', requested: true, outcome: existing.command === wrapper ? 'owned' : 'custom-preserved', healthy: true, detail: existing.command === wrapper ? 'healthy installer-owned registration' : 'healthy custom registration preserved' }; } const escaped = JSON.stringify(wrapper); @@ -237,9 +165,9 @@ const tomlHost = async (path: string, wrapper: string): Promise => { return { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `atomic config write failed: ${String(error)}` }; } const problem = await probeMcp(wrapper, ['mcp']); - return problem === null + return !isMcpProbeFailure(problem) ? { host: 'codex', requested: true, outcome: 'installed', healthy: true, detail: 'Codex config fallback added and live-verified' } - : { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem}` }; + : { host: 'codex', requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem.detail}` }; }; const hasCommand = (command: string): boolean => @@ -263,7 +191,7 @@ const cliHost = async (host: string, wrapper: string): Promise => { return { host, requested: true, outcome: 'failed', healthy: false, detail: 'codex CLI returned an unverifiable registration' }; } const problem = await probeMcp(command, args); - if (problem !== null) return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem}` }; + if (isMcpProbeFailure(problem)) return { host, requested: true, outcome: 'failed', healthy: false, detail: `existing registration is unhealthy: ${problem.detail}` }; return { host, requested: true, outcome: command === wrapper && args.length === 1 && args[0] === 'mcp' ? 'owned' : 'custom-preserved', healthy: true, detail: command === wrapper && args.length === 1 && args[0] === 'mcp' ? 'healthy installer-owned registration' : 'healthy custom registration preserved' }; } // A non-zero `get` with no registration is the Codex CLI's ordinary absence @@ -271,9 +199,9 @@ const cliHost = async (host: string, wrapper: string): Promise => { const added = commandResult('codex', ['mcp', 'add', 'commitlore', '--', wrapper, 'mcp']); if (!added.ok) return { host, requested: true, outcome: 'failed', healthy: false, detail: 'codex mcp add failed' }; const problem = await probeMcp(wrapper, ['mcp']); - return problem === null + return !isMcpProbeFailure(problem) ? { host, requested: true, outcome: 'installed', healthy: true, detail: 'Codex registration added and live-verified' } - : { host, requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem}` }; + : { host, requested: true, outcome: 'failed', healthy: false, detail: `Codex registration was written but is unhealthy: ${problem.detail}` }; }; export const inspectAndApplyHosts = async (options: Options): Promise => { diff --git a/src/core/mcp-probe.ts b/src/core/mcp-probe.ts new file mode 100644 index 00000000..0820a66c --- /dev/null +++ b/src/core/mcp-probe.ts @@ -0,0 +1,308 @@ +/** + * A small, shared CommitLore MCP identity probe. + * + * A registration proves only that a host has a command to try. This probe + * establishes the stronger fact that the command answers as CommitLore: it + * completes initialize with a name and version, then reports whether the + * advertised tools form the read-delivery or capture-initiation surface. + */ + +import { accessSync, constants, statSync } from 'node:fs'; +import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from 'node:child_process'; +import { delimiter, isAbsolute, join } from 'node:path'; + +import { installedPath } from './paths.js'; + +export type McpProbeFailureKind = + | 'command-not-found' + | 'command-is-directory' + | 'command-not-executable' + | 'command-could-not-start' + | 'command-exited' + | 'command-closed-input' + | 'initialize-timed-out' + | 'foreign-server' + | 'missing-tools' + | 'probe-unavailable'; + +export interface McpProbeFailure { + kind: 'failure'; + reason: McpProbeFailureKind; + detail: string; + cleanup?: McpProbeCleanup; +} + +export interface McpProbeSuccess { + /** The server identifies as CommitLore; kind records its advertised tool set. */ + kind: 'read-delivery' | 'capture-initiator'; + detail: string; + cleanup: McpProbeCleanup; +} + +export type McpProbeResult = McpProbeFailure | McpProbeSuccess; +export type McpProbeCleanup = 'not-needed' | 'reclaimed' | 'could-not-reclaim'; + +const failure = ( + reason: McpProbeFailureKind, + detail: string, + cleanup?: McpProbeCleanup, +): McpProbeFailure => ({ kind: 'failure', reason, detail, ...(cleanup === undefined ? {} : { cleanup }) }); + +export const MCP_READ_TOOLS = ['commitlore_query', 'commitlore_before_change'] as const; +export const MCP_CAPTURE_TOOLS = [ + 'commitlore_prepare_capture', + 'commitlore_verify_capture', + 'commitlore_stage_capture', +] as const; + +export const isMcpProbeFailure = (result: McpProbeResult): result is McpProbeFailure => result.kind === 'failure'; + +const CLEANUP_GRACE_MS = 250; + +/** + * How long a registered server gets to answer `initialize` (#640). + * + * The old value was five seconds and it was too tight to be a verdict. On a + * Windows runner a *passing* probe consumed 4478ms of it, because the chain is + * doctor -> this sidecar's node -> cmd.exe -> the server's own node: three + * process starts before a byte moves. The same registration on a slower + * machine reported `initialize-timed-out`, and doctor called it unhealthy. + * + * Fifteen seconds is not a guess at how slow a machine can be; it is a budget + * chosen so the measured worst *passing* case has room to be three times + * slower before a working registration is called broken. A genuinely dead + * command still fails long before it, through `command-exited` or + * `command-could-not-start`, which do not wait for this at all. + */ +const DEFAULT_INITIALIZE_TIMEOUT_MS = 15_000; + +/** + * Tests that assert the timeout path would otherwise wait the full budget + * twice, and a fixture that never answers is exactly what they need. The + * sidecar inherits the environment, so an override reaches the probe wherever + * it actually runs. + */ +const initializeTimeoutMs = (): number => { + const raw = Number(process.env['COMMITLORE_MCP_PROBE_TIMEOUT_MS']); + return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_INITIALIZE_TIMEOUT_MS; +}; + +const wait = (milliseconds: number): Promise => + new Promise((resolve) => setTimeout(resolve, milliseconds)); + +/** + * The synchronous wrapper cannot return until its helper exits, so a completed + * protocol exchange must also stop the server that supplied it. On POSIX the + * server receives its own process group: killing that group also handles a + * launcher which did not replace itself with the actual server process. + */ +const stopProbeChild = async (child: ChildProcessWithoutNullStreams): Promise => { + if (child.exitCode !== null || child.signalCode !== null) return 'not-needed'; + + let exitedResolve: (() => void) | undefined; + const exited = new Promise((resolve) => { + exitedResolve = resolve; + }); + child.once('exit', () => exitedResolve?.()); + + if (process.platform === 'win32') { + if (child.pid === undefined) return 'could-not-reclaim'; + const result = spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], { + stdio: 'ignore', + windowsHide: true, + }); + if (result.error !== undefined || result.status !== 0) return 'could-not-reclaim'; + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + return 'reclaimed'; + } + + const signal = (value: NodeJS.Signals): void => { + if (child.pid !== undefined) { + try { + process.kill(-child.pid, value); + return; + } catch { + // A very short-lived child can leave its process group before this + // signal. Its direct process handle is still the portable fallback. + } + } + child.kill(value); + }; + + signal('SIGTERM'); + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + if (child.exitCode === null && child.signalCode === null) { + signal('SIGKILL'); + await Promise.race([exited, wait(CLEANUP_GRACE_MS)]); + } + return child.exitCode === null && child.signalCode === null ? 'could-not-reclaim' : 'reclaimed'; +}; + +const commandPath = (command: string): string | McpProbeFailure => { + const bare = !isAbsolute(command) && !command.includes('/'); + const pathCandidates = bare + ? (process.env['PATH'] ?? '').split(delimiter).filter(Boolean).map((directory) => join(directory, command)) + : [command]; + const hasExtension = command.lastIndexOf('.') > command.lastIndexOf('/'); + const extensions = process.platform === 'win32' && bare && !hasExtension + ? (process.env['PATHEXT'] ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) + : []; + const candidates = pathCandidates.flatMap((candidate) => [ + candidate, + ...extensions.map((extension) => candidate + extension), + ]); + let nonExecutable: McpProbeFailure | undefined; + for (const candidate of candidates) { + try { + if (statSync(candidate).isDirectory()) return failure('command-is-directory', 'command is a directory'); + accessSync(candidate, constants.X_OK); + return candidate; + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + nonExecutable ??= failure('command-not-executable', 'command is not executable'); + } + } + } + return nonExecutable ?? failure('command-not-found', 'command does not exist'); +}; + +const needsWindowsCommandShell = (command: string): boolean => + process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(command); + +/** Speak enough MCP to distinguish a launchable command from usable CommitLore. */ +export const probeMcp = async (command: string, args: string[]): Promise => { + const resolved = commandPath(command); + if (typeof resolved !== 'string') return resolved; + + return new Promise((resolve) => { + let settled = false; + let child: ChildProcessWithoutNullStreams | undefined; + let timer: ReturnType | undefined; + const finish = (problem: McpProbeResult): void => { + if (settled) return; + settled = true; + if (timer !== undefined) clearTimeout(timer); + void (async () => { + const cleanup = child === undefined ? 'not-needed' : await stopProbeChild(child); + if (cleanup === 'could-not-reclaim') { + resolve(failure('probe-unavailable', 'MCP verification completed but could not reclaim its child process tree', cleanup)); + return; + } + resolve({ ...problem, cleanup }); + })(); + }; + try { + const childEnv = { ...process.env }; + delete childEnv['COMMITLORE_MCP_PROBE']; + child = spawn(resolved, args, { + stdio: ['pipe', 'pipe', 'pipe'], + shell: needsWindowsCommandShell(resolved), + env: childEnv, + detached: process.platform !== 'win32', + }); + } catch (error) { + finish(failure('command-could-not-start', `could not start command: ${error instanceof Error ? error.message : String(error)}`)); + return; + } + + let buffer = ''; + let initialized = false; + const timeoutMs = initializeTimeoutMs(); + timer = setTimeout( + () => finish(failure('initialize-timed-out', `MCP initialize timed out after ${timeoutMs}ms`)), + timeoutMs, + ); + child.once('error', (error) => finish(failure('command-could-not-start', `could not start command: ${error.message}`))); + child.once('exit', (code) => { + if (!settled) finish(failure('command-exited', `command exited before MCP verification (status ${String(code)})`)); + }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', (chunk: string) => { + buffer += chunk; + const lines = buffer.split('\n'); + buffer = lines.pop() ?? ''; + for (const line of lines) { + let message: { id?: number; result?: { serverInfo?: { name?: unknown; version?: unknown }; tools?: Array<{ name?: unknown }> } }; + try { message = JSON.parse(line) as typeof message; } catch { continue; } + if (!initialized && message.id === 1) { + const info = message.result?.serverInfo; + if (info?.name !== 'commitlore' || typeof info.version !== 'string' || info.version === '') { + finish(failure('foreign-server', 'MCP initialize did not identify CommitLore with a version')); + return; + } + initialized = true; + child?.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })}\n`); + } else if (initialized && message.id === 2) { + const tools = new Set((message.result?.tools ?? []).map((tool) => tool.name)); + if (!MCP_READ_TOOLS.every((tool) => tools.has(tool))) { + finish(failure('missing-tools', 'MCP server lacks CommitLore read-delivery tools')); + } else if (MCP_CAPTURE_TOOLS.every((tool) => tools.has(tool))) { + finish({ kind: 'capture-initiator', detail: 'MCP server advertises CommitLore read and capture tools', cleanup: 'not-needed' }); + } else { + finish({ kind: 'read-delivery', detail: 'MCP server advertises CommitLore read tools but not the complete capture tool set', cleanup: 'not-needed' }); + } + return; + } + } + }); + // A command that is not an MCP server can close before this write. Keep + // the stream error handled: EPIPE is an unhealthy registration, not an + // inspector crash (the deterministic close-stdin case covers this race). + child.stdin.on('error', () => finish(failure('command-closed-input', 'command closed its input before MCP verification'))); + // Let a launcher complete its own descriptor setup before the first + // protocol write. This preserves the probe's distinct closed-input result + // when the wrapper is the full CLI bundle rather than the former sidecar. + setTimeout(() => { + child?.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'commitlore-probe', version: '1' } } })}\n`); + }, 200); + }); +}; + +/** + * Doctor's report API is synchronous. Run this same async probe through the + * installation's declared runtime bundle rather than maintaining a second, + * subtly different protocol entry point. + */ +export const probeMcpSync = (command: string, args: string[]): McpProbeResult => { + const result = spawnSync(process.execPath, [ + installedPath('dist', 'commitlore.mjs'), + 'internal', + 'mcp-probe', + '--command', + command, + '--args-json', + JSON.stringify(args), + ], { + encoding: 'utf8', + // The helper owns the protocol timeout and needs room beyond it to reap a + // stubborn server. This outer bound is only a safety net for a broken + // helper, not the server-response timeout we report — so it is derived + // from that budget rather than written next to it, because a constant that + // silently fell below it would kill the helper before it could answer and + // report the death as the server's fault. + timeout: initializeTimeoutMs() + 2_000, + env: { ...process.env, COMMITLORE_MCP_PROBE: '1' }, + }); + if (result.error !== undefined) { + return failure('probe-unavailable', `could not run MCP verification: ${result.error.message}`); + } + try { + const parsed = JSON.parse(result.stdout) as McpProbeResult; + if ( + typeof parsed === 'object' + && parsed !== null + && typeof parsed.kind === 'string' + && typeof parsed.detail === 'string' + && ( + parsed.kind === 'read-delivery' + || parsed.kind === 'capture-initiator' + || (parsed.kind === 'failure' && typeof (parsed as Partial).reason === 'string') + ) + ) { + return parsed; + } + } catch { + // The diagnostic below includes the child status without trusting output. + } + return failure('probe-unavailable', `could not read MCP verification result (status ${String(result.status)})`); +}; diff --git a/src/core/mcp-registration.ts b/src/core/mcp-registration.ts index a0c64288..40210d58 100644 --- a/src/core/mcp-registration.ts +++ b/src/core/mcp-registration.ts @@ -70,6 +70,12 @@ const isLaunchableEntry = (value: unknown): boolean => * second: `{"command": "false"}` read as a working capture server. */ export const registeredMcpCommand = (cwd: string): string | null => { + const launch = registeredMcpLaunch(cwd); + return launch?.command ?? null; +}; + +/** The complete launch command a host will use, when its argv is parseable. */ +export const registeredMcpLaunch = (cwd: string): { command: string; args: string[] } | null => { const path = mcpRegistrationPath(cwd); if (path === null) return null; let parsed: unknown; @@ -83,7 +89,9 @@ export const registeredMcpCommand = (cwd: string): string | null => { if (!isJsonObject(servers)) return null; const entry = servers[MCP_SERVER_KEY]; if (!isLaunchableEntry(entry)) return null; - return String((entry as JsonObject)['command']); + const args = (entry as JsonObject)['args']; + if (args !== undefined && (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string'))) return null; + return { command: String((entry as JsonObject)['command']), args: (args ?? []) as string[] }; }; /** diff --git a/test/cli.test.ts b/test/cli.test.ts index 8128e3dc..144d8f9f 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -83,6 +83,11 @@ describe('commitlore CLI', () => { } }); + it('keeps the MCP probe bridge out of help', () => { + expect(runCli(['--help']).stdout).not.toContain('\n internal '); + expect(runCli(['internal', '--help']).stdout).not.toContain('\n mcp-probe '); + }); + it('says that query commands follow renames only for one path', () => { const result = runCli(['context', '--help']); diff --git a/test/doctor.test.ts b/test/doctor.test.ts index 66c2efc7..c42b5c9a 100644 --- a/test/doctor.test.ts +++ b/test/doctor.test.ts @@ -886,6 +886,21 @@ describe('doctor: cli runtime', () => { }); }); +describe('doctor: MCP probe runtime asset', () => { + it('spawns its child through the runtime asset declared by the canonical artifact', () => { + const artifact = JSON.parse( + readFileSync(join(PACKAGE_ROOT, 'installer', 'canonical-artifact.json'), 'utf8'), + ) as { runtimeAssets: string[] }; + const [runtimeAsset] = artifact.runtimeAssets; + const source = readFileSync(join(PACKAGE_ROOT, 'src', 'core', 'mcp-probe.ts'), 'utf8'); + const installedAsset = `installedPath(${runtimeAsset.split('/').map((part) => `'${part}'`).join(', ')})`; + const escapedAsset = installedAsset.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + + expect(artifact.runtimeAssets).toHaveLength(1); + expect(source).toMatch(new RegExp(`spawnSync\\(process\\.execPath,\\s*\\[\\s*${escapedAsset}`)); + }); +}); + /** * doctor is the command that answers "does this installation work", yet a * tree missing `spec/` — the same tree `validate` already refuses with exit 3 @@ -1234,6 +1249,19 @@ describe('#527 unattended capture initiator', () => { ); }; + const mcpWrapper = (repo: string, name: string): string => { + const path = join(repo, '.test-bin', name); + writeScript(path, `#!/bin/sh\nexec ${JSON.stringify(process.execPath)} ${JSON.stringify(resolve(PACKAGE_ROOT, 'dist/commitlore.mjs'))} "$@"\n`); + chmodSync(path, 0o755); + return path; + }; + + const registerMcp = (repo: string, command: string, args: string[] = ['mcp']): string => { + const path = join(repo, '.mcp.json'); + writeFileSync(path, `${JSON.stringify({ mcpServers: { commitlore: { command, args } } }, null, 2)}\n`); + return path; + }; + it('warns while nothing in the repository can start a capture', () => { const repo = initRepo('unattended-no-initiator'); enableUnattended(repo); @@ -1242,23 +1270,26 @@ describe('#527 unattended capture initiator', () => { expect(check?.status).toBe('warn'); expect(check?.detail).toContain('an ordinary git commit cannot start it'); + expect(check?.evidence?.['initiator']).toBe('agent-host-required'); }); - // The warning has to be clearable, or it fires forever on exactly the - // repositories that are configured correctly and teaches operators to - // ignore the surface that carries the real ones. - it('clears once the repository registers the capture server, and says it checked only registration', () => { + it('clears only after a registered command advertises the full capture tool set', () => { const repo = initRepo('unattended-registered'); enableUnattended(repo); - writeFileSync( - join(repo, '.mcp.json'), - `${JSON.stringify({ mcpServers: { commitlore: { command: 'commitlore', args: ['mcp'] } } }, null, 2)}\n`, - ); + const command = mcpWrapper(repo, 'commitlore'); + registerMcp(repo, command); const check = runDoctor({ cwd: repo }).checks.find((entry) => entry.id === 'unattended-initiator'); expect(check?.status).toBe('ok'); - expect(check?.evidence?.['initiator']).toBe('mcp-server-registered'); + expect(check?.evidence).toEqual({ + policy: 'unattended', + ordinary_git_commit: 'cannot-initiate', + initiator: 'capture-tools-advertised', + registration: 'custom-preserved', + verified: 'identity-and-read-and-capture-tools', + asset_readiness: 'not-verified', + }); }); it('does not accept a malformed registration as an initiator', () => { @@ -1296,38 +1327,193 @@ describe('#527 unattended capture initiator', () => { }); } - it('reports a command that is not ours as preserved, not as verified', () => { - // Preserving an operator's entry is right; calling it a working capture - // server is not. `{"command": "false"}` read as `ok` — a registration that - // launches nothing, reported as readiness. - const repo = initRepo('unattended-entry-false'); + it.each([ + ['a dead command', (repo: string) => join(repo, 'missing'), 'command-not-found'], + ['a command that is a directory', (repo: string) => repo, 'command-is-directory'], + ['a non-executable command', (repo: string) => { + const command = join(repo, '.test-bin', 'not-executable'); + writeScript(command, '#!/bin/sh\nexit 0\n'); + return command; + }, 'command-not-executable'], + // This one deliberately does not pin a probe code, and the reason is + // measured rather than assumed. Invoked on its own the probe reports + // `command-closed-input` every time — 5/5 on macOS, 10/10 on linux/amd64. + // Under the suite's load it reports `initialize-timed-out` instead, + // because the parent's write can win the race to a pipe the shell has not + // closed yet. Both are correct readings of what the probe observed; which + // one happens is the scheduler's choice, not the product's. + // + // So this case asserts the two facts that do not move — the check warns, + // and the initiator is unhealthy — and leaves the code to the case below, + // which pins `initialize-timed-out` deterministically. Asserting a code + // here would make the suite flaky; accepting either code here *and* below + // would stop the pair distinguishing "it hung up" from "it is listening + // and silent", which is the distinction worth having. + ['a command that closes its input and stays alive', (repo: string) => { + const command = join(repo, '.test-bin', 'closes-input'); + writeScript(command, '#!/bin/sh\nexec 0<&-\nexec sleep 30\n'); + chmodSync(command, 0o755); + return command; + }, null], + // The other fact, and a different one: the command keeps its input open + // and simply never answers. Nothing is closed, so the probe has to wait + // out its own budget. Merging this with the case above would lose the + // distinction between "it hung up" and "it is listening and silent". + ['a command that keeps its input open and never answers', (repo: string) => { + const command = join(repo, '.test-bin', 'never-answers'); + writeScript(command, '#!/bin/sh\nexec sleep 30\n'); + chmodSync(command, 0o755); + return command; + }, 'initialize-timed-out'], + ] as const)('reports %s distinctly', (label, commandFor, probe) => { + const repo = initRepo(`unattended-${label.replace(/\W+/g, '-')}`); enableUnattended(repo); - writeFileSync(join(repo, '.mcp.json'), '{ "mcpServers": { "commitlore": { "command": "false" } } }'); - - const row = runDoctor({ cwd: repo }).checks.find((c) => c.id === 'unattended-initiator'); + const command = commandFor(repo); + registerMcp(repo, command); + + const previousBudget = process.env['COMMITLORE_MCP_PROBE_TIMEOUT_MS']; + // These fixtures never answer on purpose, so without a shortened budget + // each waits out the real one. What is under test is the mapping below, + // not how long the product is willing to wait. Four seconds rather than + // something smaller: at 1500ms the cleanup raced the child's own start and + // the probe reported probe-unavailable, which is a different outcome and + // would have made this assert the wrong thing. + process.env['COMMITLORE_MCP_PROBE_TIMEOUT_MS'] = '4000'; + let row; + try { + row = runDoctor({ cwd: repo }).checks.find((c) => c.id === 'unattended-initiator'); + } finally { + if (previousBudget === undefined) delete process.env['COMMITLORE_MCP_PROBE_TIMEOUT_MS']; + else process.env['COMMITLORE_MCP_PROBE_TIMEOUT_MS'] = previousBudget; + } expect(row?.status).toBe('warn'); - expect(row?.detail).toContain('unverified'); - expect(row?.evidence?.['initiator']).toBe('registered-command-unverified'); + // A null expectation means the code is scheduler-dependent for this + // fixture; the case above says why. It must still be one of the probe's + // own outcomes rather than absent. + const observed = (row?.evidence as { probe?: unknown }).probe; + if (probe === null) expect(typeof observed).toBe('string'); + else expect(observed).toBe(probe); + + // #640: a timeout says the probe could not determine anything; a closed + // input says the command hung up. Only the second is a statement about the + // registration, so only the second calls it unhealthy. Deriving the + // expectation from the observed code pins the mapping itself rather than + // one side of it, and keeps the scheduler-dependent case honest. + expect(row?.evidence).toMatchObject({ + policy: 'unattended', + ordinary_git_commit: 'cannot-initiate', + initiator: + observed === 'initialize-timed-out' + ? 'registered-command-unverified' + : 'registered-command-unhealthy', + command, + }); }); - it('preserves an entry pointing somewhere other than our own command', () => { - // Not every launchable entry is the one `init` writes. A wrapper or an - // absolute path is a deliberate choice: it is left alone. What changed is - // that the report no longer calls it verified — this tool cannot say what - // somebody else's command starts. - const repo = initRepo('unattended-entry-wrapper'); + it('reports a foreign MCP server as unhealthy', () => { + const repo = initRepo('unattended-foreign-mcp'); enableUnattended(repo); - writeFileSync( - join(repo, '.mcp.json'), - '{ "mcpServers": { "commitlore": { "command": "/opt/bin/commitlore-wrapper", "args": ["mcp"] } } }', - ); + const command = join(repo, '.test-bin', 'foreign-mcp'); + writeScript(command, `#!/usr/bin/env node +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: 'foreign', version: '1.0.0' } } }) + '\\n'); + } +}); +setInterval(() => {}, 1_000); +`); + chmodSync(command, 0o755); + registerMcp(repo, command, []); + + const check = runDoctor({ cwd: repo }).checks.find((row) => row.id === 'unattended-initiator'); + + expect(check?.status).toBe('warn'); + expect(check?.evidence).toMatchObject({ initiator: 'registered-command-unhealthy', probe: 'foreign-server' }); + }); + + it('rejects a server that identifies as CommitLore but lacks required tools', () => { + const repo = initRepo('unattended-missing-tools'); + enableUnattended(repo); + const command = join(repo, '.test-bin', 'missing-tools'); + writeScript(command, `#!/usr/bin/env node +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: [] } }) + '\\n'); + } +}); +setInterval(() => {}, 1_000); +`); + chmodSync(command, 0o755); + registerMcp(repo, command, []); const check = runDoctor({ cwd: repo }).checks.find((row) => row.id === 'unattended-initiator'); expect(check?.status).toBe('warn'); - expect(check?.detail).toContain('unverified'); - // Preserved: nothing rewrote the operator's file. - expect(readFileSync(join(repo, '.mcp.json'), 'utf8')).toContain('/opt/bin/commitlore-wrapper'); + expect(check?.evidence).toMatchObject({ initiator: 'registered-command-unhealthy', probe: 'missing-tools' }); + }); + + it('reports a healthy read-delivery server distinctly when capture tools are absent', () => { + const repo = initRepo('unattended-stubborn-healthy-mcp'); + enableUnattended(repo); + const command = join(repo, '.test-bin', 'stubborn-healthy-mcp'); + writeScript(command, `#!/usr/bin/env node +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' }] } }) + '\\n'); + } +}); +process.on('SIGTERM', () => {}); +setInterval(() => {}, 1_000); +`); + chmodSync(command, 0o755); + registerMcp(repo, command, []); + + const check = runDoctor({ cwd: repo }).checks.find((row) => row.id === 'unattended-initiator'); + + expect(check?.status).toBe('warn'); + expect(check?.detail).toContain('live CommitLore read-delivery server'); + expect(check?.evidence).toEqual({ + policy: 'unattended', + ordinary_git_commit: 'cannot-initiate', + initiator: 'read-delivery-only', + registration: 'custom-preserved', + verified: 'identity-and-read-tools', + capture_tools: 'not-complete', + }); + }); + + it('preserves and accepts a healthy custom wrapper', () => { + const repo = initRepo('unattended-entry-wrapper'); + enableUnattended(repo); + const command = mcpWrapper(repo, 'operator-wrapper'); + const config = registerMcp(repo, command); + const before = readFileSync(config, 'utf8'); + + const check = runDoctor({ cwd: repo }).checks.find((row) => row.id === 'unattended-initiator'); + + expect(check?.status).toBe('ok'); + expect(check?.evidence).toMatchObject({ initiator: 'capture-tools-advertised', registration: 'custom-preserved' }); + expect(readFileSync(config, 'utf8')).toBe(before); }); }); diff --git a/test/init.test.ts b/test/init.test.ts index 5593ee64..fb83abda 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -63,6 +63,13 @@ beforeAll(() => { if (build.status !== 0) { throw new Error(`tsc build failed (exit ${build.status}):\n${build.stdout}${build.stderr}`); } + + writeFileSync( + injectCommand, + `#!/bin/sh\nexec "${process.execPath}" "${CLI_JS}" "$@"\n`, + { mode: 0o755 }, + ); + chmodSync(injectCommand, 0o755); }, 120_000); const scratch: string[] = []; @@ -78,20 +85,14 @@ const tempDir = (label: string): string => { const injectBin = tempDir('inject-bin'); const injectCommand = join(injectBin, 'commitlore'); -writeFileSync( - injectCommand, - '#!/bin/sh\nprintf \'{"hookSpecificOutput":{"additionalContext":"context"}}\\n\'\n', - { mode: 0o755 }, -); -chmodSync(injectCommand, 0o755); - -const runInitAsCli = (opts: InitOptions): InitReport => { + +const withCliEnvironment = (run: () => T): T => { const originalArgv = process.argv[1]; const originalPath = process.env['PATH']; process.argv[1] = CLI_JS; process.env['PATH'] = `${injectBin}:/usr/bin:/bin`; try { - return runInit(opts); + return run(); } finally { process.argv[1] = originalArgv; if (originalPath === undefined) delete process.env['PATH']; @@ -99,6 +100,10 @@ const runInitAsCli = (opts: InitOptions): InitReport => { } }; +const runInitAsCli = (opts: InitOptions): InitReport => withCliEnvironment(() => runInit(opts)); + +const runDoctorAsCli = (opts: { cwd: string }) => withCliEnvironment(() => runDoctor(opts)); + const git = (cwd: string, args: string[]): string => { const result = execGit(args, { cwd }); if (result.code !== 0) { @@ -214,7 +219,7 @@ describe('commitlore init — the happy path', () => { describe('commitlore init — repository MCP registration', () => { const initiatorStatus = (repo: string): string | undefined => - runDoctor({ cwd: repo }).checks.find((check) => check.id === 'unattended-initiator')?.status; + runDoctorAsCli({ cwd: repo }).checks.find((check) => check.id === 'unattended-initiator')?.status; const enableUnattended = (repo: string): void => { writeFileSync(