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' } ) } } |