From 000163dfce10677bffd5e9d1e04207bdfee430f0 Mon Sep 17 00:00:00 2001 From: Kent Date: Fri, 17 Apr 2026 22:58:04 +0800 Subject: [PATCH 1/2] feat(commission): add skill: property support for stages - Frontmatter template: skill: {plugin:skill-name} for stages with plugin-provided skills - Stage prose template: 'Load skill: Invoke Skill(...)' instruction when skill: is present - Enables workflow plugins to bind stage skills that ensign loads at dispatch time - model: was already supported; skill: completes the per-stage plugin binding --- skills/commission/SKILL.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/skills/commission/SKILL.md b/skills/commission/SKILL.md index e6cd36b99..c7d294792 100644 --- a/skills/commission/SKILL.md +++ b/skills/commission/SKILL.md @@ -219,6 +219,7 @@ stages: {feedback-to: {target_stage} — if this stage has a rejection flow that bounces back to {target_stage}. Infer from the rejection_flow derived in Confirm Design.} {gate: true — if this stage is an approval gate} {agent: {agent-name} — only if {captain} specifies a non-default agent for this stage. Omit to use the default ensign. The value is the agent file basename without .md.} + {skill: {plugin:skill-name} — only if the stage should load a specific skill from an installed plugin. The ensign will invoke Skill("{plugin:skill-name}") before starting stage work. Omit for stages without a plugin-provided skill.} - name: {last_stage} terminal: true transitions: @@ -263,6 +264,8 @@ Every {entity_label} file has YAML frontmatter. Fields are documented below; see ### `{stage_name}` +{If this stage has a `skill:` property: "**Load skill:** Invoke `Skill(\"{skill_value}\")` before starting stage work." Otherwise omit this line.} + {A sentence describing who sets this status and what it means for an {entity_label} to be in this stage.} - **Inputs:** {What the worker reads to do this stage's work — be specific to the mission} From c73ab9be4a1886b35c4a5ae7376815171ee7ff48 Mon Sep 17 00:00:00 2001 From: Kent Date: Fri, 17 Apr 2026 23:35:05 +0800 Subject: [PATCH 2/2] feat(engine): programmatic skill: binding for stage dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - status parser: add 'skill' to optional_fields so it's preserved in stage dict - claude-team build: inject Skill() loading instruction into ensign prompt when stage has skill: property (component 1.5, between header and stage def) - commission SKILL.md: document skill: property in stage template + prose template Enables workflow plugins to bind stage-specific skills that ensign loads automatically at dispatch time. Previously skill loading relied on ensign reading README prose — now it's programmatic via claude-team build. Verified: all 4 ship-flow stages (sharp/plan/execute/ship) get correct Skill() injection with matching model dispatch. --- skills/commission/bin/claude-team | 10 +++++++++- skills/commission/bin/status | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/skills/commission/bin/claude-team b/skills/commission/bin/claude-team index faffbcc6a..57d1aa0d1 100755 --- a/skills/commission/bin/claude-team +++ b/skills/commission/bin/claude-team @@ -215,12 +215,20 @@ def cmd_build(args): if stage_subsection is None: return _build_error(f"stage '{stage}' heading not found in {readme_path}") - # --- Prompt Assembly (9 components) --- + # --- Prompt Assembly (10 components) --- prompt_parts = [] # 1. Header prompt_parts.append(f'You are working on: {entity_title}\n\nStage: {stage}\n') + # 1.5. Skill loading instruction (conditional) + stage_skill = stage_meta.get('skill') + if stage_skill: + prompt_parts.append( + f'**Before starting work**, invoke `Skill("{stage_skill}")` to load ' + f'the stage skill. Follow its instructions for this stage.\n' + ) + # 2. Stage definition prompt_parts.append(f'### Stage definition:\n\n{stage_subsection}\n') diff --git a/skills/commission/bin/status b/skills/commission/bin/status index db99aca64..a9c953a52 100755 --- a/skills/commission/bin/status +++ b/skills/commission/bin/status @@ -258,7 +258,7 @@ def parse_stages_block(filepath): 'terminal': state.get('terminal', 'false').lower() == 'true', 'initial': state.get('initial', 'false').lower() == 'true', } - for optional_field in ('feedback-to', 'agent', 'fresh', 'model'): + for optional_field in ('feedback-to', 'agent', 'fresh', 'model', 'skill'): if optional_field in state: stage[optional_field] = state[optional_field] result.append(stage)