docs: trim generated prose
This commit is contained in:
@@ -1,20 +1,6 @@
|
||||
/**
|
||||
* Parse a finished hook command's process outcome (exit code + stdout + stderr)
|
||||
* into the dialect-neutral {@link HookOutput} both bridges map from.
|
||||
*
|
||||
* The exit-code contract is shared by Claude Code and Codex:
|
||||
* - exit 0 → success; if stdout is structured JSON, parse it; else the plain
|
||||
* stdout is available to the bridge (some events treat it as `additionalContext`).
|
||||
* - exit 2 → BLOCKING error; stderr is the block reason fed back to the model.
|
||||
* We surface this as `decision: 'block'` with `reason = stderr` so a bridge
|
||||
* needs no separate exit-code branch — the neutral output already says "block".
|
||||
* - other → non-blocking error; recorded (exitCode + stderr) but no decision.
|
||||
*
|
||||
* Structured-stdout fields are a SUPERSET across dialects (CC is richest); we
|
||||
* parse every field we recognize and leave it to the bridge to honor only the
|
||||
* subset meaningful for its dialect/hook point (Codex, e.g., ignores
|
||||
* `allow`/`ask`/`updatedInput`).
|
||||
*
|
||||
* Parse a finished hook command's process outcome (exit code + stdout + stderr) into the
|
||||
* dialect-neutral {@link HookOutput} both bridges map from.
|
||||
* @module @deepseek-ai/dsh-hook-protocol/codec
|
||||
*/
|
||||
|
||||
@@ -58,49 +44,26 @@ function permissionDecisionOf(value: string | undefined): HookOutput['decision']
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse one finished hook command into a {@link HookOutput}. `stdout`/`stderr`
|
||||
* are the captured streams; `exitCode` is the process exit (`undefined` when the
|
||||
* hook could not be spawned at all). Pure and total — never throws; malformed
|
||||
* JSON on a 0 exit is treated as "no structured output" (the plain stdout is
|
||||
* still on the bridge to use), matching both reference engines' lenient parse of
|
||||
* non-JSON stdout.
|
||||
*
|
||||
* `expectedEventName` is the event the hook is FIRING for (e.g. `'PreToolUse'`).
|
||||
* The reference schemas key the `hookSpecificOutput` block by `hookEventName`,
|
||||
* so a block whose `hookEventName` names a DIFFERENT event is malformed and its
|
||||
* event-scoped fields (`permissionDecision`/`permissionDecisionReason`/
|
||||
* `additionalContext`/`updatedInput`) are DISCARDED — a `PreToolUse` block on a
|
||||
* `Stop` hook must not deny the `Stop`. The block's `hookEventName` is still
|
||||
* surfaced (for the log/diagnostics), and the event-agnostic top-level fields
|
||||
* (`decision`/`reason`/`continue`/`stopReason`/`systemMessage`)
|
||||
* are unaffected. Omit `expectedEventName` (or pass a matching one) to apply the
|
||||
* block as-is — a caller that doesn't key by event opts out of the check.
|
||||
*
|
||||
* @param exitCode - the process exit code; `undefined` when the hook could not be spawned at all.
|
||||
* @param stdout - the captured stdout stream; consulted for structured JSON only on a 0 exit.
|
||||
* Decode process output into the dialect-neutral hook outcome.
|
||||
* @param exitCode - process exit, or `undefined` when spawn failed.
|
||||
* @param stdout - output parsed as structured JSON only on exit 0.
|
||||
* @param stderr - the captured stderr stream; becomes the blocking `reason` on exit 2.
|
||||
* @param expectedEventName - the event the hook is firing for; omit to apply a `hookSpecificOutput` block as-is.
|
||||
* @param expectedEventName - optional event guard for hook-specific output.
|
||||
* @returns the dialect-neutral decoded outcome.
|
||||
*/
|
||||
export function parseHookOutput(exitCode: number | undefined, stdout: string, stderr: string, expectedEventName?: string): HookOutput {
|
||||
const trimmedErr = stderr.trim()
|
||||
const trimmedOut = stdout.trim()
|
||||
// Keep the raw stdout verbatim: a clean-exit hook may emit PLAIN text the
|
||||
// protocol renders/uses (CC output; Codex SessionStart/UserPromptSubmit
|
||||
// additionalContext), so the bridge needs it even when there's no JSON.
|
||||
// Plain stdout remains available even when it is not JSON.
|
||||
const output: HookOutput = { exitCode, stderr: trimmedErr, stdout: trimmedOut }
|
||||
|
||||
// Exit 2 is a blocking error in both dialects: stderr is the reason. Surface
|
||||
// it as a `block` decision so the bridge maps it uniformly with a structured
|
||||
// `decision:'block'` — the exit code and the JSON channel converge here.
|
||||
// Both dialects treat exit 2 as a block with stderr as its reason.
|
||||
if (exitCode === BLOCKING_EXIT_CODE) {
|
||||
output.decision = 'block'
|
||||
if (trimmedErr.length > 0) output.reason = trimmedErr
|
||||
}
|
||||
|
||||
// Structured stdout is only consulted on a clean (0) exit; on a blocking exit
|
||||
// the stderr channel is authoritative. A non-zero/undefined exit other than 2
|
||||
// carries no decision (the bridge records it as a non-blocking error).
|
||||
// Structured stdout is valid only for a clean exit.
|
||||
if (exitCode === 0) {
|
||||
// Only attempt JSON when stdout looks like a JSON object — matches the
|
||||
// reference engines, which treat other stdout as plain text, not an error.
|
||||
@@ -151,12 +114,7 @@ function applyStructured(output: HookOutput, parsed: Record<string, unknown>, ex
|
||||
// mismatch — the record should show what the malformed block claimed.
|
||||
if (eventName !== undefined) output.hookEventName = eventName
|
||||
// The schemas key this block by event: when a caller passes the firing event
|
||||
// (`expectedEventName`), the block's `hookEventName` MUST name it. A different
|
||||
// name — or a MISSING one — is malformed under the keyed schema, so discard the
|
||||
// event-scoped fields (a PreToolUse block must not deny a Stop hook; nor may a
|
||||
// discriminator-less block silently apply PreToolUse-scoped permission fields to
|
||||
// whatever event is firing). A caller that passes no expectedEventName opts out
|
||||
// of the check (applies the block as-is).
|
||||
// (`expectedEventName`), the block's `hookEventName` must name it.
|
||||
if (expectedEventName !== undefined && eventName !== expectedEventName) {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user