hosts/claude/host.psd1

@{
    # Identity
    Kind          = 'claude'
    DisplayName   = 'Claude Code CLI'
    Status        = 'supported'
    SchemaVersion = 1
    MenuPriority  = 1  # Highest priority in interactive host-selection menu (iter-011)

    # Binary detection
    Binary           = 'claude'
    InstallUrl       = 'https://docs.anthropic.com/en/docs/claude-code/installation'
    InstallGuidance  = 'Claude Code CLI not found on PATH. Install: https://docs.anthropic.com/en/docs/claude-code/installation'

    # Runtime layout
    SkillRoot                  = '.claude/skills'
    HasUserSlashCommandSurface = $true
    SettingsPath               = '.claude/settings.json'
    AgentDir                   = '.claude/agents/'
    InstructionsFile           = 'CLAUDE.md'
    StructuredQuestionPrimitive = 'AskUserQuestion'
    StructuredQuestionGuidance  = 'Use Claude Code AskUserQuestion only for clarify questions, when it is available in the current session: never for a workshop question or a boundary verdict.'

    # Spec-kit + Squad coupling
    SpeckitAiFlag  = 'claude'
    PreferredAgent = 'claude'

    # Phase B handler file (not yet present)
    HandlersFile         = 'handlers.ps1'
    CoordinatorRulesFile = 'coordinator-rules.psd1'

    # Feature 171 (FR-013): refocus hook-binding declaration. Claude has the full
    # documented hook surface; it binds B1 + B2 via SessionStart and B3 via the
    # documented UserPromptSubmit event, which also captures the T070 live turn baseline. Consumed by
    # scripts/internal/deploy-refocus-hooks.ps1; deploys to the PER-USER
    # project-local settings file (C6 decision: never the shared settings.json,
    # so cloning a repo can never import auto-executing hooks).
    # NOTE: PreToolUse is deliberately ABSENT — the gate seat is dormant until
    # the first gate-kind provider row exists (F-165 coordination).
    # NOTE: PostToolUse is UNREGISTERED per TG-004 option (a), approved at the
    # iteration-001 review-signoff (measured ~920ms/call vs the 150ms bar; pwsh
    # spawn structural). B3 rides channel 1 (boundary-sync wrapper stdout) on
    # every host; iteration 002 re-evaluates (UserPromptSubmit / engine inlining).
    RefocusHookBindings = @{
        BoundTriggers       = @('b1', 'b2', 'b3')
        Events              = @('SessionStart', 'UserPromptSubmit')
        TurnStartCapability = @{ Mode = 'exact'; NativeEvent = 'UserPromptSubmit'; DispatcherEvent = 'UserPromptSubmit' }
        SettingsFile        = '.claude/settings.local.json'
        OptOutMarkerFile    = '.specrew/runtime/refocus-hooks-optout'
        DispatcherPath      = '.specify/extensions/specrew-speckit/scripts/specrew-hook-dispatcher.ps1'
        ConfigShape         = 'event-map'
        # Copilot also reads repo-local Claude settings. Guard the project-root lookup so the
        # Claude hook is a silent no-op when another host has not supplied CLAUDE_PROJECT_DIR.
        CommandMode         = 'guarded-project-encoded'
        ProjectDirPlaceholder = '${CLAUDE_PROJECT_DIR}'
        ProjectRootEnvironmentVariables = @('CLAUDE_PROJECT_DIR')
        DispatcherRuntime   = @{
            # Feature 202 iteration 002, T016 (FR-028, DA-1): the payload fields ONLY this host sends, per dispatcher event
            # ('Default' for the rest). A hook stands down only when a payload positively matches ANOTHER host's signature.
            # Measured on every recorded fixture (tests/fixtures/hook-payloads/claude): prompt and Stop carry prompt_id;
            # SessionStart carries no permission_mode, which Codex's does. No Claude payload carries a timestamp, which
            # every payload Copilot sends Claude's hooks does (B4F-144, probe/copilot-claude-hooks), so the two rules
            # Copilot's shape would otherwise also match exclude it.
            PayloadSignature    = @{
                SessionStart = @{ AllOf = @('hook_event_name', 'session_id'); NoneOf = @('permission_mode', 'sessionId', 'timestamp') }
                PostToolUse  = @{ AllOf = @('hook_event_name', 'session_id', 'tool_name'); NoneOf = @('turn_id', 'sessionId', 'timestamp') }
                Default      = @{ AllOf = @('hook_event_name', 'session_id', 'prompt_id'); NoneOf = @('turn_id', 'sessionId') }
            }
            BootstrapDeliveryEvents = @('SessionStart')
            B3DeliveryEvents        = @('PostToolUse', 'UserPromptSubmit')
            RefocusTriggerByEvent   = @{ PostToolUse = 'b3'; UserPromptSubmit = 'b3' }
            SuppressedRefocusEvents = @()
            OutputShape             = 'plain-or-hookSpecificOutput'
            DecisionOnlyEvents      = @()
            # FR-004 (185) stop-block lever (verified, research/stop-block-capability-matrix.md): Claude Stop
            # supports {"decision":"block","reason":...} + a built-in stop_hook_active / 8-block loop cap.
            StopBlockShape          = 'decision-block'
            BootstrapDeliveryMode   = 'pointer'
            # Feature 202 T002 (DA-1, option B): this host's INPUT contract is data. The dispatcher's single
            # normalizer (ConvertTo-SpecrewTurnRecord) reads it; shared code names no host. Field paths may be
            # dotted (a.b.c). Measured payloads: tests/fixtures/hook-payloads/<host>/ (202 research.md, T001).
            InputFieldMap           = @{
                SessionId      = @('session_id')
                Prompt         = @('prompt')
                # Feature 202 iteration 002, T017 (FR-027, DA-2): the host's own ID for a typed message, the same on the
                # prompt event and on that message's transcript entry, so both recording paths key it the same way.
                MessageId      = @('prompt_id')
                TranscriptPath = @('transcript_path')
                Source         = @('source')
                ToolName       = @('tool_name')
            }
            # Evaluated in order; the first matching rule decides. A prompt that matches no host-injected rule
            # is typed-user, and only typed-user can produce a receipt (FR-006). hook-output-journal: the prompt
            # equals text this dispatcher emitted (a replayed Stop reason). prompt-pattern: a host envelope or a
            # replayed Specrew directive.
            OriginRules             = @(
                # Feature 202 iteration 002, T028 (B4F-147): a sub-agent's task text reaches the prompt hook as a prompt. A payload
                # carrying a sub-agent field was not typed by the human, whatever its text, so it can never be an approval.
                # Claude marks a sub-agent's payloads agent_id (probe/claude, 2.1.303).
                @{ Origin = 'host-injected'; Rule = 'payload-field'; Fields = @('agent_id', 'parent_agent_id') },
                @{ Origin = 'host-injected'; Rule = 'hook-output-journal' },
                @{ Origin = 'host-injected'; Rule = 'prompt-pattern'; Pattern = '(?is)^\s*<(?:task-notification|hook_prompt|turn_aborted|system-reminder|environment_context)\b' },
                @{ Origin = 'host-injected'; Rule = 'prompt-pattern'; Pattern = '(?is)^\s*(?:Specrew:|Specrew\s+review\s+[-\u2014]|\[specrew-|AWAITING YOUR VERDICT:|BOUNDARY NOT READY FOR A VERDICT:|Campaign review authority\b)' },
                @{ Origin = 'typed-user'; Rule = 'prompt-present' }
            )
        }
        Registrations       = @(
            @{ Event = 'SessionStart'; DispatcherEvent = 'SessionStart'; HandlerShape = 'hooks-array' },
            @{ Event = 'UserPromptSubmit'; DispatcherEvent = 'UserPromptSubmit'; HandlerShape = 'hooks-array' },
            @{ Event = 'Stop'; DispatcherEvent = 'Stop'; HandlerShape = 'hooks-array' },
            @{ Event = 'PostToolUse'; DispatcherEvent = 'PostToolUse'; HandlerShape = 'hooks-array' }
        )
    }
}