mirror of
https://github.com/CharlesWiltgen/Axiom.git
synced 2026-09-20 19:58:20 +08:00
8a7086351a
The third and last capability the ledger recorded as downgraded, replaced by a static preamble in each agent file. Cursor's subagentStart response accepts additional_context and its query carries subagent_type, which is the only field the canonical hook reads, so the port is a field rename. This is additive, not a replacement. The generated preamble carries an agent's own declared skills; the hook carries the general skill-awareness text gated by agent type. Both now ship. Verified as far as a free Cursor plan allows: the hook registers, fires, and delivers a payload carrying subagent_type, task, and subagent_model — confirmed against a live Cursor 3.17.8 delegation, which fires subagentStart three times before refusing to start the subagent with "Named models unavailable. Free plans can only use Auto." What a free plan cannot show is whether the returned context reaches a subagent that never starts. The docs and the submission matrix say so rather than implying full verification. If delivery does not work, the preamble still carries the declared skills, so the failure mode is the status quo rather than a regression. The context guard needed widening: skill awareness is legitimately multi-paragraph, where the router and crash hints are single-line. Newline and tab are now permitted, every other control character is still rejected, and the bound is separate at 8 KiB.
137 lines
5.9 KiB
Python
Generated
137 lines
5.9 KiB
Python
Generated
#!/usr/bin/env python3
|
|
"""SubagentStart hook for Axiom plugin.
|
|
|
|
Injects compact Axiom skill awareness into subagents so they use skills.
|
|
|
|
Standalone Python (matching pretool-crash-route.py / posttool-bash-hints.py /
|
|
user-prompt-submit.py) — NOT embedded in a bash heredoc. The heredoc-in-bash
|
|
pattern breaks under macOS bash 3.2 whenever a prose apostrophe lands in the
|
|
body; plain .py avoids that and is directly lintable/testable.
|
|
|
|
Reads a JSON payload on stdin, writes a JSON response on stdout. Never exits
|
|
non-zero — a hook failure must not block subagent startup.
|
|
|
|
RESERVED AGENT-TYPE SUFFIX: an agent whose type ends in `-noskills` receives no
|
|
injection. It is reserved for agents that must measure un-assisted behavior —
|
|
A/B testing a skill's value needs a control arm that genuinely lacks the skill.
|
|
The SubagentStart payload exposes only `agent_type` (no prompt text, no
|
|
per-invocation env), so agent naming is the only channel available for this.
|
|
Name an agent `-noskills` only when you intend it; the suppression is silent
|
|
apart from a stderr note.
|
|
|
|
SCOPE: this suppresses THIS hook only. Axiom's PostToolUse hooks still fire for
|
|
such an agent — `posttool-bash-hints.py` appends skill hints to Bash output and
|
|
`swift-guardrails.py` can block a Write/Edit citing an Axiom rule — and their
|
|
payloads carry no `agent_type` to gate on. An agent that must stay clean should
|
|
also be denied Bash/Write/Edit in its own definition.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
input_data = json.load(sys.stdin)
|
|
agent_type = input_data.get("agent_type", "")
|
|
except Exception:
|
|
print("{}")
|
|
sys.exit(0)
|
|
|
|
# Project-type gate (GH #48). Like session-start.py / user-prompt-submit.py, stay
|
|
# silent in non-Apple projects and honor AXIOM_SESSION_CONTEXT — otherwise a
|
|
# generic subagent (general-purpose, Explore, ...) spun up in a Python or docs
|
|
# repo gets Axiom iOS routing pressure injected into its context. Fail-open in
|
|
# BOTH directions: a missing module or detection error falls through to injection
|
|
# rather than silencing a real Apple project (resolve_context_decision is itself
|
|
# fail-open). stdin is already drained above, so this early exit leaves no unread
|
|
# pipe. CPython puts this script's dir on sys.path[0] regardless of cwd; the
|
|
# explicit insert only hardens the unusual -c / -m / symlink invocation.
|
|
_hook_dir = os.path.dirname(os.path.abspath(__file__))
|
|
if _hook_dir not in sys.path:
|
|
sys.path.insert(0, _hook_dir)
|
|
try:
|
|
from project_detect import resolve_context_decision
|
|
|
|
if not resolve_context_decision(os.getcwd(), os.environ.get("AXIOM_SESSION_CONTEXT")):
|
|
print("{}")
|
|
sys.exit(0)
|
|
except Exception:
|
|
pass # fail-open: detection unavailable → proceed with skill injection
|
|
|
|
# Skip agents that won't benefit from Axiom skills
|
|
skip_types = {
|
|
"statusline-setup",
|
|
"claude-code-guide",
|
|
"episodic-memory:search-conversations",
|
|
"beads:task-agent",
|
|
"plugin-dev:skill-reviewer",
|
|
"plugin-dev:plugin-validator",
|
|
"plugin-dev:agent-creator",
|
|
"plugin-dev:skill-development",
|
|
"plugin-dev:command-development",
|
|
"plugin-dev:hook-development",
|
|
"plugin-dev:plugin-structure",
|
|
"plugin-dev:agent-development",
|
|
"plugin-dev:plugin-settings",
|
|
"plugin-dev:mcp-integration",
|
|
"plugin-dev:create-plugin",
|
|
"code-simplifier:code-simplifier",
|
|
}
|
|
|
|
if agent_type in skip_types:
|
|
print("{}")
|
|
sys.exit(0)
|
|
|
|
# Also skip any agent type containing known non-iOS plugin prefixes
|
|
skip_prefixes = ("beads:", "plugin-dev:", "superpowers-lab:", "superpowers-developing-for-claude-code:")
|
|
if any(agent_type.startswith(p) for p in skip_prefixes):
|
|
print("{}")
|
|
sys.exit(0)
|
|
|
|
# Reserved "-noskills" suffix (see module docstring): the agent is deliberately
|
|
# measuring UNSKILLED behavior, so injecting the roster would corrupt it. The
|
|
# SubagentStart payload carries only agent_type — never the prompt — so a prompt
|
|
# saying "do not use Axiom skills" cannot suppress this hook. A naming convention
|
|
# is the only discriminator available. Announce on stderr so a surprise
|
|
# suppression is debuggable; stderr is advisory and does not affect the stdout
|
|
# contract at exit 0.
|
|
if agent_type.endswith("-noskills"):
|
|
sys.stderr.write(
|
|
f"axiom: no skill injection for '{agent_type}' (reserved -noskills suffix)\n"
|
|
)
|
|
print("{}")
|
|
sys.exit(0)
|
|
|
|
context = """You have access to Axiom iOS development skills via the Skill tool. If your task involves iOS, Swift, Xcode, or Apple frameworks, invoke the matching skill BEFORE doing the work:
|
|
|
|
- `axiom-build` — build failures, Xcode, simulator, SPM
|
|
- `axiom-swiftui` — SwiftUI views, navigation, layout, animation, architecture
|
|
- `axiom-data` — SwiftData, Core Data, CloudKit, migrations, Codable
|
|
- `axiom-concurrency` — async/await, actors, Sendable, data races
|
|
- `axiom-performance` — memory leaks, profiling, battery, Instruments
|
|
- `axiom-networking` — URLSession, Network.framework, HTTP
|
|
- `axiom-integration` — widgets, Siri, StoreKit, EventKit, push, background tasks
|
|
- `axiom-media` — camera, photos, audio, haptics, ShazamKit, Now Playing
|
|
- `axiom-accessibility` — VoiceOver, Dynamic Type, WCAG
|
|
- `axiom-ai` — Foundation Models, Apple Intelligence
|
|
- `axiom-games` — SpriteKit, SceneKit, RealityKit
|
|
- `axiom-shipping` — App Store submission, rejections, privacy manifests
|
|
- `axiom-macos` — macOS windows, menus, sandboxing, distribution, AppKit bridging
|
|
- `axiom-design` — HIG patterns, Liquid Glass, SF Symbols, typography, app structure
|
|
- `axiom-swift` — Swift idioms, noncopyable types, drag and drop, tvOS
|
|
- `axiom-uikit` — UIKit/SwiftUI bridging, Auto Layout, Combine, TextKit
|
|
- `axiom-location` — Core Location, MapKit, geofencing, directions
|
|
|
|
Invoke with: Skill tool, skill name (e.g., "axiom-swiftui")."""
|
|
|
|
output = {
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "SubagentStart",
|
|
"additionalContext": context,
|
|
}
|
|
}
|
|
|
|
print(json.dumps(output))
|